diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 2929f16816..5e51267ffb 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -532,6 +532,9 @@ class OverrideDuplicateMove(bpy.types.Operator): def _execute(self, context): self.new_active_obj = None + # Track decompositions so they can be recreated after the operation + relationships = tool.Root.get_decomposition_relationships(context.selected_objects) + old_to_new = {} for obj in context.selected_objects: new_obj = obj.copy() if obj.data: @@ -542,8 +545,12 @@ class OverrideDuplicateMove(bpy.types.Operator): collection.objects.link(new_obj) obj.select_set(False) new_obj.select_set(True) - # This is the only difference - blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj) + # Copy the actual class + new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj) + if new: + old_to_new[tool.Ifc.get_entity(obj)] = new + # Recreate decompositions + tool.Root.recreate_decompositions(relationships, old_to_new) bpy.ops.transform.translate("INVOKE_DEFAULT") blenderbim.bim.handler.purge_module_data() @@ -580,6 +587,9 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator): def _execute(self, context): self.new_active_obj = None + # Track decompositions so they can be recreated after the operation + relationships = tool.Root.get_decomposition_relationships(context.selected_objects) + old_to_new = {} for obj in context.selected_objects: new_obj = obj.copy() if obj == context.active_object: @@ -588,8 +598,12 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator): collection.objects.link(new_obj) obj.select_set(False) new_obj.select_set(True) - # This is the only difference - blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj) + # Copy the actual class + new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj) + if new: + old_to_new[tool.Ifc.get_entity(obj)] = new + # Recreate decompositions + tool.Root.recreate_decompositions(relationships, old_to_new) bpy.ops.transform.translate("INVOKE_DEFAULT") blenderbim.bim.handler.purge_module_data() return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/void/ui.py b/src/blenderbim/blenderbim/bim/module/void/ui.py index e1cf7037a0..128073a4ed 100644 --- a/src/blenderbim/blenderbim/bim/module/void/ui.py +++ b/src/blenderbim/blenderbim/bim/module/void/ui.py @@ -60,8 +60,6 @@ class BIM_PT_voids(Panel): obj_name = obj.name if opening_id and obj_name: op = row.operator("bim.remove_opening", icon="X", text="Remove Opening").opening_id = opening_id - else: - row.label(text="Select an opening and an element to modify", icon="HELP") opening_ids = Data.products[props.ifc_definition_id] if not opening_ids and not active_object_is_an_opening: diff --git a/src/blenderbim/blenderbim/core/root.py b/src/blenderbim/blenderbim/core/root.py index a96ad1d3a4..5919036335 100644 --- a/src/blenderbim/blenderbim/core/root.py +++ b/src/blenderbim/blenderbim/core/root.py @@ -30,9 +30,15 @@ def copy_class(ifc, collector, geometry, root, obj=None): root.link_object_data(ifc.get_object(relating_type), obj) elif representation: root.copy_representation(element, new) + new_representation = root.get_element_representation(new, root.get_representation_context(representation)) + data = geometry.duplicate_object_data(obj) + geometry.change_object_data(obj, data, is_global=True) + geometry.rename_object(data, geometry.get_representation_name(new_representation)) + geometry.link(new_representation, data) collector.assign(obj) if root.is_opening_element(new): root.add_tracked_opening(obj) + return new def assign_class( diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index faaae9502d..6dd74b103e 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -446,12 +446,15 @@ class Root: def add_tracked_opening(cls, obj): pass def copy_representation(cls, source, dest): pass def does_type_have_representations(cls, element): pass + def get_decomposition_relationships(cls, objs): pass + def get_element_representation(cls, element, context): pass def get_element_type(cls, element): pass def get_object_name(cls, obj): pass def get_object_representation(cls, obj): pass def get_representation_context(cls, representation): pass def is_opening_element(cls, element): pass def link_object_data(cls, source_obj, destination_obj): pass + def recreate_decompositions(cls, relationships, old_to_new): pass def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass def set_element_specific_display_settings(cls, obj, element): pass def set_object_name(cls, obj, element): pass diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index 217ffb8399..bba0cabad5 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -50,6 +50,29 @@ class Root(blenderbim.core.tool.Root): def does_type_have_representations(cls, element): return bool(element.RepresentationMaps) + @classmethod + def get_decomposition_relationships(cls, objs): + relationships = {} + for obj in objs: + element = tool.Ifc.get_entity(obj) + if not element: + continue + if hasattr(element, "FillsVoids") and element.FillsVoids: + building = element.FillsVoids[0].RelatingOpeningElement.VoidsElements[0].RelatingBuildingElement + relationships[element] = {"type": "fill", "element": building} + return relationships + + @classmethod + def get_element_representation(cls, element, context): + if context.is_a("IfcGeometricRepresentationSubContext"): + return ifcopenshell.util.representation.get_representation( + element, + context=context.ContextType, + subcontext=context.ContextIdentifier, + target_view=context.TargetView, + ) + return ifcopenshell.util.representation.get_representation(element, context=context.ContextType) + @classmethod def get_element_type(cls, element): return ifcopenshell.util.element.get_type(element) @@ -80,6 +103,18 @@ class Root(blenderbim.core.tool.Root): def link_object_data(cls, source_obj, destination_obj): destination_obj.data = source_obj.data + @classmethod + def recreate_decompositions(cls, relationships, old_to_new): + for subelement, data in relationships.items(): + subelement = old_to_new.get(subelement) + element = old_to_new.get(data["element"]) + if not subelement or not element: + continue + if data["type"] == "fill": + obj1 = tool.Ifc.get_object(element) + obj2 = tool.Ifc.get_object(subelement) + bpy.ops.bim.add_filled_opening(voided_obj=obj1.name, filling_obj=obj2.name) + @classmethod def run_geometry_add_representation( cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None diff --git a/src/blenderbim/test/core/test_root.py b/src/blenderbim/test/core/test_root.py index 5fc2ca7844..92d7e94562 100644 --- a/src/blenderbim/test/core/test_root.py +++ b/src/blenderbim/test/core/test_root.py @@ -51,6 +51,13 @@ class TestCopyClass: root.get_element_type("element").should_be_called().will_return("type") root.does_type_have_representations("type").should_be_called().will_return(False) root.copy_representation("original_element", "element").should_be_called() + root.get_representation_context("representation").should_be_called().will_return("context") + root.get_element_representation("element", "context").should_be_called().will_return("new_representation") + geometry.change_object_data("obj", "data", is_global=True).should_be_called() + geometry.get_representation_name("new_representation").should_be_called().will_return("name") + geometry.rename_object("data", "name").should_be_called() + geometry.link("new_representation", "data").should_be_called() + geometry.duplicate_object_data("obj").should_be_called().will_return("data") collector.assign("obj").should_be_called() root.is_opening_element("element").should_be_called().will_return(False) subject.copy_class(ifc, collector, geometry, root, obj="obj") diff --git a/src/blenderbim/test/tool/test_root.py b/src/blenderbim/test/tool/test_root.py index 7946a9db26..615a1b42c7 100644 --- a/src/blenderbim/test/tool/test_root.py +++ b/src/blenderbim/test/tool/test_root.py @@ -64,6 +64,33 @@ class TestDoesTypeHaveRepresentations(NewFile): assert subject.does_type_have_representations(element) is True +class TestGetDecompositionRelationships(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + + element = ifc.createIfcWall() + opening = ifc.createIfcOpeningElement() + fill = ifc.createIfcWindow() + ifcopenshell.api.run("void.add_opening", ifc, opening=opening, element=element) + ifcopenshell.api.run("void.add_filling", ifc, opening=opening, element=fill) + + obj = bpy.data.objects.new("Object", None) + tool.Ifc.link(fill, obj) + + assert subject.get_decomposition_relationships([obj]) == {fill: {"type": "fill", "element": element}} + + +class TestGetElementRepresentation(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + context = ifc.createIfcGeometricRepresentationContext(ContextType="Model") + representation = ifc.createIfcShapeRepresentation(ContextOfItems=context) + wall = ifc.createIfcWall(Representation=ifc.createIfcProductDefinitionShape(Representations=[representation])) + assert subject.get_element_representation(wall, context) == representation + + class TestGetElementType(NewFile): def test_run(self): bpy.ops.bim.create_project()