mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-30 08:33:10 +00:00
New demo module to teach developers how to hack on the BlenderBIM Add-on.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# This allows us to write full integration tests that test how all systems work
|
||||
# as a whole. Whilst other tests focus on portions of the software, these tests
|
||||
# simulate what happens when a user opens Blender, presses buttons, and does
|
||||
# things.
|
||||
|
||||
# These tests read like english. You can see all the possible sentences defined
|
||||
# in test_feature.py. Most of the time, there is already a sentence defined for
|
||||
# what you want to test.
|
||||
|
||||
@demo
|
||||
Feature: Demo
|
||||
|
||||
# Every operator has at least one scenario associated with it to test it.
|
||||
Scenario: Demonstrate hello world
|
||||
Given an empty IFC project
|
||||
When I press "bim.demonstrate_hello_world"
|
||||
# Blender doesn't have a way of testing that things are visible in the
|
||||
# interface and layout. We can check properties, and whats in the 3D
|
||||
# scenegraph, but not layout. There is no "DOM" like in web applications.
|
||||
# Too bad, we can't check the results, but we still write the test, that way
|
||||
# we can still check for errors like crashes or Python errors, like a "smoke
|
||||
# test".
|
||||
Then nothing happens
|
||||
|
||||
# This operator has two scenarios because there are two possibilities of a user
|
||||
# interacting with it.
|
||||
Scenario: Demonstrate rename project - with a name provided
|
||||
Given an empty IFC project
|
||||
When I set "scene.BIMDemoProperties.name" to "Foobar"
|
||||
And I press "bim.demonstrate_rename_project"
|
||||
Then the object "IfcProject/Foobar" is an "IfcProject"
|
||||
|
||||
# This is the other possible scenario for the rename project operator.
|
||||
Scenario: Demonstrate rename project - with no name
|
||||
Given an empty IFC project
|
||||
When I set "scene.BIMDemoProperties.name" to ""
|
||||
And I press "bim.demonstrate_rename_project"
|
||||
Then the object "IfcProject/My Project" is an "IfcProject"
|
||||
@@ -209,6 +209,19 @@ def i_set_prop_to_value(prop, value):
|
||||
exec(f"bpy.context.{prop} = {value}")
|
||||
|
||||
|
||||
@given(parsers.parse('I set "{prop}" to ""'))
|
||||
@when(parsers.parse('I set "{prop}" to ""'))
|
||||
def i_set_prop_to_value(prop):
|
||||
try:
|
||||
eval(f"bpy.context.{prop}")
|
||||
except:
|
||||
assert False, "Property does not exist"
|
||||
try:
|
||||
exec(f'bpy.context.{prop} = r""')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@when(parsers.parse('I am on frame "{number}"'))
|
||||
def i_am_on_frame_number(number):
|
||||
bpy.context.scene.frame_set(int(number))
|
||||
|
||||
@@ -70,6 +70,13 @@ def debug():
|
||||
prophet.verify()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def demo():
|
||||
prophet = Prophecy(blenderbim.core.tool.Demo)
|
||||
yield prophet
|
||||
prophet.verify()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def drawing():
|
||||
prophet = Prophecy(blenderbim.core.tool.Drawing)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Testing the core might seem strange if you haven't written this type of
|
||||
# abstract test before. You essentially want to test that things are called in
|
||||
# the right sequence. This seems almost like writing the code twice, like in
|
||||
# double entry bookkeeping in accounting. However, it guards against typos,
|
||||
# helps document intention of different logical flows, and ensures that all
|
||||
# permutations of logic flows meet sanity checks.
|
||||
|
||||
# We always call the module we're testing the "test subject". This makes our
|
||||
# tests simple to read: just look for where the "subject" is called!
|
||||
import blenderbim.core.demo as subject
|
||||
# These are like mocks, stubs, or spy objects. They don't do anything, but they
|
||||
# let us check our test expectations.
|
||||
from test.core.bootstrap import ifc, demo
|
||||
|
||||
|
||||
# Let's test the hello world function.
|
||||
class TestDemonstrateHelloWorld:
|
||||
# This function has only one logical flow, there is no benefit to describing
|
||||
# it further, so we have a test_run function. We need to specify all the
|
||||
# mock objects we need in the signature.
|
||||
def test_run(self, demo):
|
||||
# We set an expectation that in this default sequence of events, the
|
||||
# demo tool should have the set_message called with the "Hello, World!"
|
||||
# string as its argument. Notice how our test also reads like English.
|
||||
demo.set_message("Hello, World!").should_be_called()
|
||||
# After we've finished specifying our test expectations, let's run the
|
||||
# test subject!
|
||||
subject.demonstrate_hello_world(demo)
|
||||
|
||||
|
||||
# Another test, but this time with two logical flows.
|
||||
class TestDemonstrateRenameProject:
|
||||
# The default logical flow is where we rename the project successfully.
|
||||
def test_renaming_the_project(self, ifc, demo):
|
||||
# This time, we describe an expectation that the demo tool should have
|
||||
# its get_project() function called with no attributes. When it is
|
||||
# called, we expect it to return "project" as a string.
|
||||
|
||||
# This might sound strange. How is get_project implemented? Does it
|
||||
# actually return a string? We don't know, and we don't care. That's a
|
||||
# detail that our core isn't interested in. All our core is interested
|
||||
# in is that we get back a project - we're arbitrarily using a string to
|
||||
# represent it.
|
||||
demo.get_project().should_be_called().will_return("project")
|
||||
# Here, again, we're not interested in the details. However, we are
|
||||
# interested in checking that the project we previously retrieved is
|
||||
# passed verbatim into the Ifc tool.
|
||||
ifc.run("attribute.edit_attributes", product="project", attributes={"Name": "name"}).should_be_called()
|
||||
demo.clear_name_field().should_be_called()
|
||||
demo.hide_user_hints().should_be_called()
|
||||
# Let's test our test subject! Notice that just like the arbitrary
|
||||
# string "project", we've specified an arbitrary input string of "name".
|
||||
subject.demonstrate_rename_project(ifc, demo, name="name")
|
||||
|
||||
# One alternative flow is when no name is provided. What happens then?
|
||||
def test_showing_a_hint_if_no_name_provided(self, ifc, demo):
|
||||
demo.show_user_hints().should_be_called()
|
||||
subject.demonstrate_rename_project(ifc, demo, name=None)
|
||||
@@ -0,0 +1,87 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
|
||||
# out if you have any questions or need further guidance. Happy hacking!
|
||||
|
||||
# ############################################################################ #
|
||||
|
||||
# Because our tools have well defined, isolated functions, it means we can test
|
||||
# them very easily in isolation. Tests are fun, fast, and easy to setup!
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from test.bim.bootstrap import NewFile
|
||||
from blenderbim.tool.demo import Demo as subject
|
||||
|
||||
|
||||
# Our first test is that our tool implements all the abstract methods defined by
|
||||
# the `core/tool.py` interface. Anytime the core wants something that the tools
|
||||
# don't provide, this test will catch it. This type of test comes for free in
|
||||
# other languages, but not Python, so we test it explicitly.
|
||||
class TestImplementsTool(NewFile):
|
||||
def test_run(self):
|
||||
assert isinstance(subject(), blenderbim.core.tool.Demo)
|
||||
|
||||
|
||||
# These are fairly boring tests. What it does demonstrate is that no matter how
|
||||
# complex a BIM application can get, it can always be reduced down to small,
|
||||
# easily tested functions. Note that these functions actually are concrete
|
||||
# implementations, so that means that you need to use Blender headlessly to run
|
||||
# these tests.
|
||||
class TestClearNameField(NewFile):
|
||||
def test_run(self):
|
||||
bpy.context.scene.BIMDemoProperties.name = "name"
|
||||
subject.clear_name_field()
|
||||
assert bpy.context.scene.BIMDemoProperties.name == ""
|
||||
|
||||
|
||||
class TestGetProject(NewFile):
|
||||
def test_run(self):
|
||||
# Sometimes, there is a bit of preparation work to setup a scenario that
|
||||
# can be tested. In this case, we need to set an active IFC dataset with
|
||||
# a project. This is normal.
|
||||
ifc = ifcopenshell.file()
|
||||
project = ifc.createIfcProject()
|
||||
tool.Ifc.set(ifc)
|
||||
assert subject.get_project() == project
|
||||
|
||||
|
||||
class TestHideUserHints(NewFile):
|
||||
def test_run(self):
|
||||
bpy.context.scene.BIMDemoProperties.show_hints = True
|
||||
subject.hide_user_hints()
|
||||
assert bpy.context.scene.BIMDemoProperties.show_hints == False
|
||||
|
||||
|
||||
class TestSetMessage(NewFile):
|
||||
def test_run(self):
|
||||
bpy.context.scene.BIMDemoProperties.message = ""
|
||||
subject.set_message("message")
|
||||
assert bpy.context.scene.BIMDemoProperties.message == "message"
|
||||
|
||||
|
||||
class TestShowUserHints(NewFile):
|
||||
def test_run(self):
|
||||
bpy.context.scene.BIMDemoProperties.show_hints = False
|
||||
subject.show_user_hints()
|
||||
assert bpy.context.scene.BIMDemoProperties.show_hints == True
|
||||
Reference in New Issue
Block a user