From a29d691944a9cd29fb1a1f43630d8114c849d631 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 21 Sep 2021 15:19:18 +1000 Subject: [PATCH] Fix #1737. Bug where copying an opening didn't copy relevant boolean modifiers too. --- .../blenderbim/bim/module/root/operator.py | 24 ++++++++++++++++--- .../test/bim/module/root/test_operator.py | 18 ++++++++++++++ .../test/api/root/test_copy_class.py | 14 +++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 006e5105cc..3470c45cbb 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -21,6 +21,7 @@ import ifcopenshell import ifcopenshell.api import ifcopenshell.util.schema import ifcopenshell.util.element +import blenderbim.bim.handler from ifcopenshell.api.void.data import Data as VoidData from blenderbim.bim.ifc import IfcStore @@ -310,11 +311,14 @@ class CopyClass(bpy.types.Operator): else: bpy.ops.bim.add_representation(obj=obj.name) if result.is_a("IfcSpatialElement") or result.is_a("IfcSpatialStructureElement"): - self.place_in_spatial_collection(old_element, obj) + self.place_in_spatial_collection(result, obj) + elif result.is_a("IfcOpeningElement"): + self.add_opening_modifiers(result, obj) + blenderbim.bim.handler.purge_module_data() return {"FINISHED"} - def place_in_spatial_collection(self, old_element, obj): - aggregate = ifcopenshell.util.element.get_aggregate(old_element) + def place_in_spatial_collection(self, element, obj): + aggregate = ifcopenshell.util.element.get_aggregate(element) if not aggregate: return container_obj = IfcStore.get_element(aggregate.id()) @@ -327,3 +331,17 @@ class CopyClass(bpy.types.Operator): new = bpy.data.collections.new(obj.name) new.objects.link(obj) collection.children.link(new) + + def add_opening_modifiers(self, result, obj): + for rel in result.VoidsElements: + building_obj = IfcStore.get_element(rel.RelatingBuildingElement.id()) + try: + modifier = next(m for m in obj.modifiers if m.type == "BOOLEAN" and m.object == obj) + except StopIteration: + modifier = building_obj.modifiers.new("IfcOpeningElement", "BOOLEAN") + modifier.object = obj + finally: + modifier.operation = "DIFFERENCE" + modifier.solver = "EXACT" + modifier.use_self = True + modifier.operand_type = "OBJECT" diff --git a/src/blenderbim/test/bim/module/root/test_operator.py b/src/blenderbim/test/bim/module/root/test_operator.py index d7183b551e..e43a6c1688 100644 --- a/src/blenderbim/test/bim/module/root/test_operator.py +++ b/src/blenderbim/test/bim/module/root/test_operator.py @@ -107,3 +107,21 @@ class TestCopyClass(test.bim.bootstrap.NewFile): And the object "IfcBuildingStorey/My Storey.001" is in the collection "IfcBuildingStorey/My Storey.001" And the collection "IfcBuildingStorey/My Storey.001" is in the collection "IfcBuilding/My Building" """ + + @test.bim.bootstrap.scenario + def test_copying_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 "Cube" is selected + And additionally the object "IfcWall/Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + And the object "IfcOpeningElement/Cube" is selected + And I duplicate the selected objects + Then the object "IfcOpeningElement/Cube" and "IfcOpeningElement/Cube.001" are different elements + And the object "IfcWall/Cube" has a boolean difference by "IfcOpeningElement/Cube" + And the object "IfcWall/Cube" has a boolean difference by "IfcOpeningElement/Cube.001" + """ diff --git a/src/ifcopenshell-python/test/api/root/test_copy_class.py b/src/ifcopenshell-python/test/api/root/test_copy_class.py index df93e2f48f..19a02821b0 100644 --- a/src/ifcopenshell-python/test/api/root/test_copy_class.py +++ b/src/ifcopenshell-python/test/api/root/test_copy_class.py @@ -32,6 +32,13 @@ class TestCopyClass(test.bootstrap.IFC4): assert element.IsDecomposedBy assert not new.IsDecomposedBy + def test_copying_an_aggregate_decomposition(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcElementAssembly") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBeam") + ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) + new = ifcopenshell.api.run("root.copy_class", self.file, product=subelement) + assert new.Decomposes[0].RelatingObject == element + def test_not_copying_any_representations_because_life_is_hard(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element.Representation = self.file.createIfcProductDefinitionShape() @@ -56,3 +63,10 @@ class TestCopyClass(test.bootstrap.IFC4): ifcopenshell.api.run("void.add_filling", self.file, opening=opening, element=door) new = ifcopenshell.api.run("root.copy_class", self.file, product=opening) assert not new.HasFillings + + def test_copying_a_filling(self): + door = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor") + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_filling", self.file, opening=opening, element=door) + new = ifcopenshell.api.run("root.copy_class", self.file, product=door) + assert not new.FillsVoids