From bdbd874fa8c9f32fea473921d0275a4ff40d4c74 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 9 Oct 2022 18:09:22 +1100 Subject: [PATCH] Fix #2488. Copying objects now copy deep instead of adding afresh (and losing parametric data and openings). --- .../blenderbim/bim/module/geometry/ui.py | 12 ---------- src/blenderbim/blenderbim/core/root.py | 20 ++++++----------- src/blenderbim/blenderbim/core/tool.py | 1 + src/blenderbim/blenderbim/tool/root.py | 16 ++++++++++++++ src/blenderbim/test/core/test_root.py | 20 +++++------------ src/blenderbim/test/tool/test_root.py | 18 +++++++++++++++ .../ifcopenshell-python/code_examples.rst | 22 +++++-------------- .../ifcopenshell/api/root/copy_class.py | 2 +- 8 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/ui.py b/src/blenderbim/blenderbim/bim/module/geometry/ui.py index be05825288..fce82d0112 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/ui.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/ui.py @@ -127,18 +127,6 @@ class BIM_PT_mesh(Panel): layout = self.layout props = context.active_object.data.BIMMeshProperties - row = layout.row(align=True) - op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT") - op.should_switch_all_meshes = True - op.should_reload = True - op.ifc_definition_id = props.ifc_definition_id - op.disable_opening_subtractions = False - op = row.operator("bim.switch_representation", text="Dynamic Voids", icon="SELECT_INTERSECT") - op.should_switch_all_meshes = True - op.should_reload = True - op.ifc_definition_id = props.ifc_definition_id - op.disable_opening_subtractions = True - row = layout.row() row.operator("bim.copy_representation") diff --git a/src/blenderbim/blenderbim/core/root.py b/src/blenderbim/blenderbim/core/root.py index 67e1ebe856..a96ad1d3a4 100644 --- a/src/blenderbim/blenderbim/core/root.py +++ b/src/blenderbim/blenderbim/core/root.py @@ -22,22 +22,16 @@ def copy_class(ifc, collector, geometry, root, obj=None): if not element: return representation = root.get_object_representation(obj) - element = ifc.run("root.copy_class", product=element) - ifc.link(element, obj) - relating_type = root.get_element_type(element) + new = ifc.run("root.copy_class", product=element) + ifc.link(new, obj) + relating_type = root.get_element_type(new) if relating_type and root.does_type_have_representations(relating_type): - ifc.run("type.map_type_representations", related_object=element, relating_type=relating_type) + ifc.run("type.map_type_representations", related_object=new, relating_type=relating_type) root.link_object_data(ifc.get_object(relating_type), obj) - else: - if representation: - root.run_geometry_add_representation( - obj=obj, - context=root.get_representation_context(representation), - ifc_representation_class=geometry.get_ifc_representation_class(element, representation), - profile_set_usage=geometry.get_profile_set_usage(element), - ) + elif representation: + root.copy_representation(element, new) collector.assign(obj) - if root.is_opening_element(element): + if root.is_opening_element(new): root.add_tracked_opening(obj) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index a8b9318722..faaae9502d 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -444,6 +444,7 @@ class Resource: @interface 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_element_type(cls, element): pass def get_object_name(cls, obj): pass diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index 34faeb540f..217ffb8399 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -30,6 +30,22 @@ class Root(blenderbim.core.tool.Root): new = bpy.context.scene.BIMModelProperties.openings.add() new.obj = obj + @classmethod + def copy_representation(cls, source, dest): + if dest.is_a("IfcProduct"): + if not source.Representation: + return + dest.Representation = ifcopenshell.util.element.copy_deep( + tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"] + ) + elif dest.is_a("IfcTypeProduct"): + if not source.RepresentationMaps: + return + dest.RepresentationMaps = [ + ifcopenshell.util.element.copy_deep(tool.Ifc.get(), m, exclude=["IfcGeometricRepresentationContext"]) + for m in source.RepresentationMaps + ] + @classmethod def does_type_have_representations(cls, element): return bool(element.RepresentationMaps) diff --git a/src/blenderbim/test/core/test_root.py b/src/blenderbim/test/core/test_root.py index f2f35f5a65..5fc2ca7844 100644 --- a/src/blenderbim/test/core/test_root.py +++ b/src/blenderbim/test/core/test_root.py @@ -40,25 +40,17 @@ class TestCopyClass: root.is_opening_element("element").should_be_called().will_return(False) subject.copy_class(ifc, collector, geometry, root, obj="obj") - def test_copy_with_new_geometry_added_afresh_for_speed(self, ifc, collector, geometry, root): + def test_copy_with_new_geometry_copied_from_the_old(self, ifc, collector, geometry, root): + # Originally, geometry was added fresh from the Blender mesh instead of + # copied. This was faster (though I cannot recreate it now) but had the + # bigger problem of not preserving non-mesh geometry and openings. ifc.get_entity("obj").should_be_called().will_return("original_element") root.get_object_representation("obj").should_be_called().will_return("representation") ifc.run("root.copy_class", product="original_element").should_be_called().will_return("element") ifc.link("element", "obj").should_be_called() root.get_element_type("element").should_be_called().will_return("type") root.does_type_have_representations("type").should_be_called().will_return(False) - - root.get_representation_context("representation").should_be_called().will_return("context") - geometry.get_ifc_representation_class("element", "representation").should_be_called().will_return( - "ifc_representation_class" - ) - geometry.get_profile_set_usage("element").should_be_called().will_return("profile_set_usage") - root.run_geometry_add_representation( - obj="obj", - context="context", - ifc_representation_class="ifc_representation_class", - profile_set_usage="profile_set_usage", - ).should_be_called() + root.copy_representation("original_element", "element").should_be_called() 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") @@ -74,7 +66,7 @@ class TestCopyClass: root.is_opening_element("element").should_be_called().will_return(False) subject.copy_class(ifc, collector, geometry, root, obj="obj") - def test_copied_openings_have_dynamic_voids_added(self, ifc, collector, root): + def test_copied_openings_are_tracked_for_special_visualiation(self, ifc, collector, root): ifc.get_entity("obj").should_be_called().will_return("original_element") root.get_object_representation("obj").should_be_called().will_return(None) ifc.run("root.copy_class", product="original_element").should_be_called().will_return("element") diff --git a/src/blenderbim/test/tool/test_root.py b/src/blenderbim/test/tool/test_root.py index abde5e5a17..7946a9db26 100644 --- a/src/blenderbim/test/tool/test_root.py +++ b/src/blenderbim/test/tool/test_root.py @@ -37,6 +37,24 @@ class TestAddTrackedOpening(NewFile): assert props.openings[0].obj == obj +class TestCopyRepresentation(NewFile): + def test_copying_a_product(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + source = ifc.createIfcWall(Representation=ifc.createIfcProductDefinitionShape()) + dest = ifc.createIfcWall() + subject.copy_representation(source, dest) + assert dest.Representation.is_a("IfcProductDefinitionShape") + + def test_copying_a_type_product(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + source = ifc.createIfcWallType(RepresentationMaps=[ifc.createIfcRepresentationMap()]) + dest = ifc.createIfcWallType() + subject.copy_representation(source, dest) + assert dest.RepresentationMaps[0].is_a("IfcRepresentationMap") + + class TestDoesTypeHaveRepresentations(NewFile): def test_run(self): ifc = ifcopenshell.file() diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/code_examples.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/code_examples.rst index 90d2dfb3ec..55ba4e3ca1 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/code_examples.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/code_examples.rst @@ -178,8 +178,7 @@ Create a simple model from scratch model = ifcopenshell.file() # All projects must have one IFC Project element - project = run("root.create_entity", model, ifc_class="IfcProject") - project.Name = "My Project" + project = run("root.create_entity", model, ifc_class="IfcProject", name="My Project") # Geometry is optional in IFC, but because we want to use geometry in this example, let's define units # Assigning without arguments defaults to metric units @@ -189,23 +188,14 @@ Create a simple model from scratch context = run("context.add_context", model, context_type="Model") # In particular, in this example we want to store the 3D "body" geometry of objects, i.e. the body shape body = run( - "context.add_context", - model, - context_type="Model", - context_identifier="Body", - target_view="MODEL_VIEW", - parent=context, + "context.add_context", model, + context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=context ) # Create a site, building, and storey. Many hierarchies are possible. - site = run("root.create_entity", model, ifc_class="IfcSite") - building = run("root.create_entity", model, ifc_class="IfcBuilding") - storey = run("root.create_entity", model, ifc_class="IfcBuildingStorey") - - # Let's name them to be neat. - site.Name = "My Site" - building.Name = "Building A" - storey.Name = "Ground Floor" + site = run("root.create_entity", model, ifc_class="IfcSite", name="My Site") + building = run("root.create_entity", model, ifc_class="IfcBuilding", name="Building A") + storey = run("root.create_entity", model, ifc_class="IfcBuildingStorey", name="Ground Floor") # Since the site is our top level location, assign it to the project # Then place our building on the site, and our storey in the building diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py index 4224bfb95b..99a89231d0 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py @@ -80,7 +80,7 @@ class Usecase: # For now, we do copy opening representations if opening.Representation: new_opening.Representation = ifcopenshell.util.element.copy_deep( - self.file, opening.Representation + self.file, opening.Representation, exclude=["IfcGeometricRepresentationContext"] ) elif inverse.is_a("IfcRelFillsElement"): continue