From 33bc0be5ff8873470f25e4727bdec45cce5f0485 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 9 Sep 2021 12:54:43 +1000 Subject: [PATCH] Non dynamically voided objects now auto update when their opening is removed --- .../blenderbim/bim/module/void/operator.py | 11 ++- src/blenderbim/test/bim/bootstrap.py | 66 +++++++++++++++- .../test/bim/module/void/__init__.py | 0 .../test/bim/module/void/test_operator.py | 76 +++++++++++++++++++ 4 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 src/blenderbim/test/bim/module/void/__init__.py create mode 100644 src/blenderbim/test/bim/module/void/test_operator.py diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index 292aa1466b..b814ec01d6 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -21,7 +21,6 @@ import ifcopenshell.api import ifcopenshell.util.representation from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.void.data import Data -from ifcopenshell.api.context.data import Data as ContextData class AddOpening(bpy.types.Operator): @@ -87,6 +86,7 @@ class RemoveOpening(bpy.types.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = IfcStore.get_file() + is_modifier_removed = False for modifier in obj.modifiers: if modifier.type != "BOOLEAN": continue @@ -94,10 +94,19 @@ class RemoveOpening(bpy.types.Operator): IfcStore.unlink_element(obj=modifier.object) if "/" in modifier.object.name and modifier.object.name[0:3] == "Ifc": modifier.object.name = "/".join(modifier.object.name.split("/")[1:]) + is_modifier_removed = True obj.modifiers.remove(modifier) break ifcopenshell.api.run("void.remove_opening", self.file, **{"opening": self.file.by_id(self.opening_id)}) + + if not is_modifier_removed: + bpy.ops.bim.switch_representation( + ifc_definition_id=obj.data.BIMMeshProperties.ifc_definition_id, + should_reload=True, + should_switch_all_meshes=True, + ) + Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) return {"FINISHED"} diff --git a/src/blenderbim/test/bim/bootstrap.py b/src/blenderbim/test/bim/bootstrap.py index bbc97191ad..ee7ae7e194 100644 --- a/src/blenderbim/test/bim/bootstrap.py +++ b/src/blenderbim/test/bim/bootstrap.py @@ -60,11 +60,19 @@ def i_add_a_cube(): bpy.ops.mesh.primitive_cube_add() +def i_add_a_cube_of_size_size_at_location(size, location): + bpy.ops.mesh.primitive_cube_add(size=float(size), location=[float(co) for co in location.split(",")]) + + def the_object_name_is_selected(name): + bpy.ops.object.select_all(action="DESELECT") + additionally_the_object_name_is_selected(name) + + +def additionally_the_object_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) @@ -81,7 +89,10 @@ def i_enable_prop(prop): def i_press_operator(operator): - exec(f"bpy.ops.{operator}()") + if "(" in operator: + exec(f"bpy.ops.{operator}") + else: + exec(f"bpy.ops.{operator}()") def the_object_name_exists(name): @@ -153,14 +164,53 @@ def the_file_name_should_contain_value(name, value): assert value in f.read() +def the_object_name1_has_a_boolean_difference_by_name2(name1, name2): + obj = the_object_name_exists(name1) + for modifier in obj.modifiers: + if modifier.type == "BOOLEAN" and modifier.object.name == name2: + return True + assert False, "No boolean found" + + +def the_object_name1_has_no_boolean_difference_by_name2(name1, name2): + obj = the_object_name_exists(name1) + for modifier in obj.modifiers: + if modifier.type == "BOOLEAN" and modifier.object.name == name2: + assert False, "A boolean was found" + + +def the_object_name_is_voided_by_void(name, void): + ifc = IfcStore.get_file() + element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id) + for rel in element.HasOpenings: + if rel.RelatedOpeningElement.Name == void: + return True + assert False, "No void found" + + +def the_object_name_is_not_voided_by_void(name, void): + ifc = IfcStore.get_file() + element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id) + for rel in element.HasOpenings: + if rel.RelatedOpeningElement.Name == void: + assert False, "A void was found" + def the_object_name_should_display_as_mode(name, mode): assert the_object_name_exists(name).display_type == mode + +def the_object_name_has_number_vertices(name, number): + total = len(the_object_name_exists(name).data.vertices) + assert total == int(number), f"We found {total} vertices" + + definitions = { "an empty IFC project": an_empty_ifc_project, "I add a cube": i_add_a_cube, + 'I add a cube of size "([0-9]+)" at "(.*)"': i_add_a_cube_of_size_size_at_location, 'the object "(.*)" is selected': the_object_name_is_selected, + 'additionally the object "(.*)" is selected': additionally_the_object_name_is_selected, 'I set "(.*)" to "(.*)"': i_set_prop_to_value, 'I enable "(.*)"': i_enable_prop, 'I press "(.*)"': i_press_operator, @@ -174,20 +224,28 @@ definitions = { "I duplicate the selected objects": i_duplicate_the_selected_objects, 'the object "(.*)" and "(.*)" are different elements': the_object_name1_and_name2_are_different_elements, 'the file "(.*)" should contain "(.*)"': the_file_name_should_contain_value, + 'the object "(.*)" has a boolean difference by "(.*)"': the_object_name1_has_a_boolean_difference_by_name2, + 'the object "(.*)" has no boolean difference by "(.*)"': the_object_name1_has_no_boolean_difference_by_name2, + 'the object "(.*)" is voided by "(.*)"': the_object_name_is_voided_by_void, + 'the object "(.*)" is not voided by "(.*)"': the_object_name_is_not_voided_by_void, 'the object "(.*)" should display as "(.*)"': the_object_name_should_display_as_mode, + 'the object "(.*)" has "([0-9]+)" vertices': the_object_name_has_number_vertices, } # Super lightweight Gherkin implementation def run(scenario): + keywords = ["Given", "When", "Then", "And", "But"] for line in scenario.split("\n"): - line = line.strip() line = line.replace("{cwd}", os.getcwd()) + for keyword in keywords: + line = line.replace(keyword, "") + line = line.strip() if not line: continue match = None for definition, callback in definitions.items(): - match = re.search(definition, line) + match = re.search("^" + definition + "$", line) if match: try: callback(*match.groups()) diff --git a/src/blenderbim/test/bim/module/void/__init__.py b/src/blenderbim/test/bim/module/void/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/blenderbim/test/bim/module/void/test_operator.py b/src/blenderbim/test/bim/module/void/test_operator.py new file mode 100644 index 0000000000..10140257d9 --- /dev/null +++ b/src/blenderbim/test/bim/module/void/test_operator.py @@ -0,0 +1,76 @@ +# 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 TestAddOpening(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_adding_an_opening(self): + return """ + Given an empty IFC project + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube + And the object "IfcWall/Cube" is selected + And additionally the object "Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + Then the object "IfcOpeningElement/Cube" is an "IfcOpeningElement" + And the object "IfcOpeningElement/Cube" should display as "WIRE" + And the object "IfcWall/Cube" has a boolean difference by "IfcOpeningElement/Cube" + And the object "IfcWall/Cube" is voided by "Cube" + """ + + +class TestRemoveOpening(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_removing_an_opening(self): + return """ + Given an empty IFC project + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube of size "1" at "1,0,0" + And the object "IfcWall/Cube" is selected + And additionally the object "Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + And I press "bim.remove_opening(opening_id=97, obj='IfcWall/Cube')" + Then the object "IfcWall/Cube" has no boolean difference by "IfcOpeningElement/Cube" + And the object "IfcWall/Cube" is not voided by "Cube" + """ + + @test.bim.bootstrap.scenario + def test_removing_a_non_dynamic_opening(self): + return """ + Given an empty IFC project + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube of size "1" at "1,0,0" + And the object "IfcWall/Cube" is selected + And additionally the object "Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + And the object "IfcWall/Cube" is selected + And I press "bim.print_ifc_file" + And I press "bim.switch_representation(ifc_definition_id=86, should_reload=True)" + Then the object "IfcWall/Cube" has no boolean difference by "IfcOpeningElement/Cube" + And the object "IfcWall/Cube" has "16" vertices + When I press "bim.remove_opening(opening_id=97, obj='IfcWall/Cube')" + Then the object "IfcWall/Cube" has "8" vertices + """