From 6a2307308cda6c9bb925544af2e7d2c264482bbd Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 28 Jan 2023 21:51:00 +1100 Subject: [PATCH] Fix bug where copying objects with profiles should only share profiles if the profile is named. --- .../blenderbim/bim/module/model/slab.py | 3 ++- src/blenderbim/blenderbim/tool/root.py | 7 +++++-- .../ifcopenshell/util/element.py | 20 +++++++++++++++---- .../test/api/context/test_add_context.py | 4 ++-- .../test/util/test_element.py | 7 +++++++ 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index 03f7213db0..66bdca4b75 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -654,7 +654,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): if not profile: def msg(self, context): - self.layout.label(text="INVALID PROFILE: " + indices[1]) + self.layout.label(text="INVALID PROFILE") bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") DecorationsHandler.install(context) @@ -667,6 +667,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile) blenderbim.core.geometry.switch_representation( + tool.Ifc, tool.Geometry, obj=obj, representation=representation, diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index 920e6b07c5..42a39f8881 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -50,18 +50,21 @@ class Root(blenderbim.core.tool.Root): @classmethod def copy_representation(cls, source, dest): + def exclude_callback(attribute): + return attribute.is_a("IfcProfileDef") and attribute.ProfileName + if dest.is_a("IfcProduct"): if not source.Representation: return dest.Representation = ifcopenshell.util.element.copy_deep( - tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext", "IfcProfileDef"] + tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"], exclude_callback=exclude_callback ) elif dest.is_a("IfcTypeProduct"): if not source.RepresentationMaps: return dest.RepresentationMaps = [ ifcopenshell.util.element.copy_deep( - tool.Ifc.get(), m, exclude=["IfcGeometricRepresentationContext", "IfcProfileDef"] + tool.Ifc.get(), m, exclude=["IfcGeometricRepresentationContext"], exclude_callback=exclude_callback ) for m in source.RepresentationMaps ] diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 9c79156091..540da598fb 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -666,7 +666,7 @@ def copy(ifc_file, element): return new -def copy_deep(ifc_file, element, exclude=None): +def copy_deep(ifc_file, element, exclude=None, exclude_callback=None): """ Recursively copy an element and all of its directly related subelements. @@ -679,6 +679,10 @@ def copy_deep(ifc_file, element, exclude=None): :param exclude: An optional list of strings of IFC class names to not copy. If any of the subelement is this class, it will not be copied and the original instance will be referenced. + :type exclude: list[str],optional + :param exclude_callback: A callback to determine whether or not to exclude + an entity or not. Returns True to exclude and False to exclude. + :type exclude_callback: function,optional :return: The newly copied element :rtype: ifcopenshell.entity_instance.entity_instance """ @@ -687,13 +691,21 @@ def copy_deep(ifc_file, element, exclude=None): if attribute is None: continue if isinstance(attribute, ifcopenshell.entity_instance): - if not exclude or (exclude and not any([attribute.is_a(e) for e in exclude])): + if exclude and any([attribute.is_a(e) for e in exclude]): + pass + elif exclude_callback and exclude_callback(attribute): + pass + else: attribute = copy_deep(ifc_file, attribute, exclude=exclude) elif isinstance(attribute, tuple) and attribute and isinstance(attribute[0], ifcopenshell.entity_instance): - if not exclude or (exclude and not any([attribute[0].is_a(e) for e in exclude])): + if exclude and any([attribute[0].is_a(e) for e in exclude]): + pass + elif exclude_callback and exclude_callback(attribute[0]): + pass + else: attribute = list(attribute) for j, item in enumerate(attribute): - attribute[j] = copy_deep(ifc_file, item, exclude=exclude) + attribute[j] = copy_deep(ifc_file, item, exclude=exclude, exclude_callback=exclude_callback) if new.attribute_name(i) == "GlobalId": new[i] = ifcopenshell.guid.new() else: diff --git a/src/ifcopenshell-python/test/api/context/test_add_context.py b/src/ifcopenshell-python/test/api/context/test_add_context.py index a825440abe..c44ae9d0f9 100644 --- a/src/ifcopenshell-python/test/api/context/test_add_context.py +++ b/src/ifcopenshell-python/test/api/context/test_add_context.py @@ -38,8 +38,8 @@ class TestAddContext(test.bootstrap.IFC4): assert context.ContextType == "Plan" assert context.is_a() == "IfcGeometricRepresentationContext" assert context.WorldCoordinateSystem.is_a() == "IfcAxis2Placement2D" - assert context.WorldCoordinateSystem.Location.Coordinates == (0, 0, 0) - assert context.WorldCoordinateSystem.RefDirection.DirectionRatios == (1, 0, 0) + assert context.WorldCoordinateSystem.Location.Coordinates == (0, 0) + assert context.WorldCoordinateSystem.RefDirection.DirectionRatios == (1, 0) assert context.CoordinateSpaceDimension == 2 def test_defaulting_to_3d_with_an_unknown_context_type(self): diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 9b3410646c..9d5a1f1589 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -700,3 +700,10 @@ class TestCopyDeepIFC4(test.bootstrap.IFC4): rel.RelatedObjects = [element] rel2 = subject.copy_deep(self.file, rel, exclude=["IfcWall"]) assert rel.RelatedObjects == rel2.RelatedObjects + + def test_copying_an_element_recursively_with_aggregates_with_an_exclude_callback(self): + element = self.file.createIfcWall(Name="name") + rel = self.file.createIfcRelAggregates() + rel.RelatedObjects = [element] + rel2 = subject.copy_deep(self.file, rel, exclude_callback=lambda x: x.is_a("IfcWall")) + assert rel.RelatedObjects == rel2.RelatedObjects