From 6bf9b8b0c980c3ba658a907c38ce072630614100 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 9 May 2025 15:24:17 +1000 Subject: [PATCH] Fix bug where appending assets could accidentally create duplicate contexts --- .../ifcopenshell/api/project/append_asset.py | 20 +++++++++++------- .../test/api/project/test_append_asset.py | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py index d92c50135d..00c11dc72c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py @@ -506,7 +506,9 @@ class Usecase: def reuse_existing_contexts(self) -> None: added_contexts = set([e for e in self.added_elements.values() if e.is_a("IfcGeometricRepresentationContext")]) added_contexts -= set(self.existing_contexts) - for added_context in added_contexts: + sorted_added_contexts = [c for c in added_contexts if c.is_a() == "IfcGeometricRepresentationContext"] + sorted_added_contexts.extend([c for c in added_contexts if c.is_a() == "IfcGeometricRepresentationSubContext"]) + for added_context in sorted_added_contexts: equivalent_existing_context = self.get_equivalent_existing_context(added_context) if not equivalent_existing_context: equivalent_existing_context = self.create_equivalent_context(added_context) @@ -541,18 +543,22 @@ class Usecase: parent = self.get_equivalent_existing_context(added_context.ParentContext) if not parent: parent = self.create_equivalent_context(added_context.ParentContext) - return ifcopenshell.api.context.add_context( + self.existing_contexts.append(parent) + context = ifcopenshell.api.context.add_context( self.file, parent=parent, context_type=added_context.ContextType, context_identifier=added_context.ContextIdentifier, target_view=added_context.TargetView, ) - return ifcopenshell.api.context.add_context( - self.file, - context_type=added_context.ContextType, - context_identifier=added_context.ContextIdentifier, - ) + else: + context = ifcopenshell.api.context.add_context( + self.file, + context_type=added_context.ContextType, + context_identifier=added_context.ContextIdentifier, + ) + self.existing_contexts.append(context) + return context def file_add( self, element: ifcopenshell.entity_instance, conversion_factor: Optional[float] = None diff --git a/src/ifcopenshell-python/test/api/project/test_append_asset.py b/src/ifcopenshell-python/test/api/project/test_append_asset.py index 3a937cc4c3..3028269931 100644 --- a/src/ifcopenshell-python/test/api/project/test_append_asset.py +++ b/src/ifcopenshell-python/test/api/project/test_append_asset.py @@ -81,6 +81,27 @@ class TestAppendAssetIFC2X3(test.bootstrap.IFC2X3): # make sure it's still valid assert context.WorldCoordinateSystem + def test_add_new_contexts_if_necessary(self): + library = ifcopenshell.api.project.create_file(version=self.file.schema) + ifcopenshell.api.root.create_entity(library, ifc_class="IfcProject") + lib_context = ifcopenshell.api.context.add_context(library, context_type="Model") + lib_subcontext = ifcopenshell.api.context.add_context(library, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=lib_context) + + # It has no contexts right now + ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") + + material = ifcopenshell.api.material.add_material(library, name="Material") + style = ifcopenshell.api.style.add_style(library) + ifcopenshell.api.style.assign_material_style(library, material=material, style=style, context=lib_subcontext) + + ifcopenshell.api.project.append_asset(self.file, library=library, element=material) + assert len(self.file.by_type("IfcProject")) == 1 + assert len(self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)) == 1 + assert len(self.file.by_type("IfcGeometricRepresentationSubContext", include_subtypes=False)) == 1 + context = self.file.by_type("IfcMaterial")[0].HasRepresentation[0].Representations[0].ContextOfItems + # make sure it's still valid + assert context.WorldCoordinateSystem + def test_append_a_single_type_product_even_though_an_inverse_material_relationship_is_shared(self): library = ifcopenshell.api.project.create_file(version=self.file.schema) element = ifcopenshell.api.root.create_entity(library, ifc_class="IfcWallType")