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