diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py
index 5a2113fa08..b27cc03f7c 100644
--- a/src/blenderbim/blenderbim/bim/export_ifc.py
+++ b/src/blenderbim/blenderbim/bim/export_ifc.py
@@ -114,7 +114,7 @@ class IfcExporter:
IfcStore.edited_objs.clear()
def sync_object_placement(self, obj):
- blender_matrix = np.matrix(obj.matrix_world)
+ blender_matrix = np.array(obj.matrix_world)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not hasattr(element, "ObjectPlacement"):
return
diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py
index 544357b7c4..d230398dfa 100644
--- a/src/blenderbim/blenderbim/bim/module/root/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/root/operator.py
@@ -117,11 +117,11 @@ class AssignClass(bpy.types.Operator):
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
+ if not self.ifc_class:
+ self.ifc_class = context.scene.BIMRootProperties.ifc_class
self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
if self.predefined_type == "USERDEFINED":
self.predefined_type = self.userdefined_type
- elif self.predefined_type == "":
- predefined_type = None
for obj in objects:
self.assign_class(context, obj)
return {"FINISHED"}
@@ -134,7 +134,7 @@ class AssignClass(bpy.types.Operator):
self.file,
**{
"ifc_class": self.ifc_class,
- "predefined_type": self.predefined_type,
+ "predefined_type": self.predefined_type or None,
"name": obj.name,
},
)
diff --git a/src/blenderbim/test/bim/bootstrap.py b/src/blenderbim/test/bim/bootstrap.py
index 8f232ac681..a1f7897575 100644
--- a/src/blenderbim/test/bim/bootstrap.py
+++ b/src/blenderbim/test/bim/bootstrap.py
@@ -17,12 +17,109 @@
# along with BlenderBIM Add-on. If not, see .
+import re
import bpy
import pytest
from blenderbim.bim.ifc import IfcStore
+
class NewFile:
@pytest.fixture(autouse=True)
def setup(self):
IfcStore.purge()
bpy.ops.wm.read_homefile(app_template="")
+
+
+def scenario(function):
+ def subfunction(self):
+ run(function(self))
+
+ return subfunction
+
+
+def an_empty_ifc_project():
+ bpy.ops.bim.create_project()
+
+
+def i_add_a_cube():
+ bpy.ops.mesh.primitive_cube_add()
+
+
+def the_object_named_name_is_selected(name):
+ obj = bpy.context.scene.objects.get(name)
+ if not obj:
+ assert False, 'The object "{name}" could not be selected'
+ bpy.ops.object.select_all(action="DESELECT")
+ bpy.context.view_layer.objects.active = obj
+ obj.select_set(True)
+
+
+def i_select_value_in_prop(value, prop):
+ exec(f'bpy.context.{prop} = "{value}"')
+
+
+def i_press_operator(operator):
+ exec(f"bpy.ops.{operator}()")
+
+
+def the_object_named_name_exists(name):
+ obj = bpy.data.objects.get(name)
+ if not obj:
+ assert False, f'The object "{name}" does not exist'
+ return obj
+
+
+def an_ifc_file_exists():
+ ifc = IfcStore.get_file()
+ if not ifc:
+ assert False, "No IFC file is available"
+ return ifc
+
+
+def the_object_named_name_is_an_ifc_class(name, ifc_class):
+ ifc = an_ifc_file_exists()
+ element = ifc.by_id(the_object_named_name_exists(name).BIMObjectProperties.ifc_definition_id)
+ assert element.is_a(ifc_class), f'Object "{name}" is a {element.is_a()}'
+
+
+definitions = {
+ "an empty IFC project": an_empty_ifc_project,
+ "I add a cube": i_add_a_cube,
+ 'the object named "(.*)" is selected': the_object_named_name_is_selected,
+ 'I select "(.*)" in "(.*)"': i_select_value_in_prop,
+ 'I press "(.*)"': i_press_operator,
+ 'the object named "(.*)" is an "(.*)"': the_object_named_name_is_an_ifc_class,
+ "an IFC file exists": an_ifc_file_exists,
+}
+
+
+# Super lightweight Gherkin implementation
+def run(scenario):
+ for line in scenario.split("\n"):
+ line = line.strip()
+ if not line:
+ continue
+ match = None
+ for definition, callback in definitions.items():
+ match = re.search(definition, line)
+ if match:
+ try:
+ callback(*match.groups())
+ except AssertionError as e:
+ assert False, f"Failed: {line}, with error: {e}"
+ break
+ if not match:
+ assert False, f"Definition not implemented: {line}"
+ return True
+
+
+def run_debug(scenario, blend_filepath=None):
+ try:
+ result = run(scenario)
+ except Exception as e:
+ if blend_filepath:
+ bpy.ops.wm.save_as_mainfile(filepath=blend_filepath)
+ assert False, e
+ if blend_filepath:
+ bpy.ops.wm.save_as_mainfile(filepath=blend_filepath)
+ return result
diff --git a/src/blenderbim/test/bim/module/project/test_operator.py b/src/blenderbim/test/bim/module/project/test_operator.py
index a995246b90..5542f4ac7c 100644
--- a/src/blenderbim/test/bim/module/project/test_operator.py
+++ b/src/blenderbim/test/bim/module/project/test_operator.py
@@ -16,14 +16,17 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
-import bpy
-import pytest
import test.bim.bootstrap
-from blenderbim.bim.ifc import IfcStore
class TestCreateProject(test.bim.bootstrap.NewFile):
+ @test.bim.bootstrap.scenario
def test_creating_a_project(self):
- assert IfcStore.get_file() is None
- bpy.ops.bim.create_project()
- assert IfcStore.get_file()
+ return """
+ When I press "bim.create_project"
+ Then an IFC file exists
+ And the object named "IfcProject/My Project" is an "IfcProject"
+ And the object named "IfcSite/My Site" is an "IfcSite"
+ And the object named "IfcBuilding/My Building" is an "IfcBuilding"
+ And the object named "IfcBuildingStorey/My Storey" is an "IfcBuildingStorey"
+ """
diff --git a/src/blenderbim/test/bim/module/root/__init__.py b/src/blenderbim/test/bim/module/root/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/blenderbim/test/bim/module/root/test_operator.py b/src/blenderbim/test/bim/module/root/test_operator.py
new file mode 100644
index 0000000000..7f395b2f0e
--- /dev/null
+++ b/src/blenderbim/test/bim/module/root/test_operator.py
@@ -0,0 +1,32 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2021 Dion Moult
+#
+# 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 .
+
+import test.bim.bootstrap
+
+
+class TestCreateProject(test.bim.bootstrap.NewFile):
+ @test.bim.bootstrap.scenario
+ def test_assigning_a_class_to_a_cube(self):
+ return """
+ Given an empty IFC project
+ When I add a cube
+ And the object named "Cube" is selected
+ And I select "IfcWall" in "scene.BIMRootProperties.ifc_class"
+ And I press "bim.assign_class"
+ And the object named "IfcWall/Cube" is an "IfcWall"
+ """
diff --git a/src/blenderbim/test/bim/test_operator.py b/src/blenderbim/test/bim/test_operator.py
index 88446f10bf..608be4185d 100644
--- a/src/blenderbim/test/bim/test_operator.py
+++ b/src/blenderbim/test/bim/test_operator.py
@@ -17,7 +17,6 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
-import pytest
import test.bim.bootstrap
from blenderbim.bim.ifc import IfcStore