From f43e33b96943e13d064896129446c805bc0dd7c5 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 28 Jan 2025 15:07:06 +0500 Subject: [PATCH] apped_asset - fix passed removed elements after 3ffc0bc #6044 Before 3ffc0bc checking identities were handled by file.add but now it's handled append_asset, therefore should be a way to clean removed entities. --- .../ifcopenshell/api/project/append_asset.py | 40 +++++++++++++++++-- .../test/api/project/test_append_asset.py | 16 ++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py index ceeda11066..3600a7c981 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py @@ -142,6 +142,37 @@ def append_asset( return usecase.execute() +class SafeRemovalContext: + file: ifcopenshell.file + reuse_identities: dict[int, ifcopenshell.entity_instance] + + def __init__(self, ifc_file: ifcopenshell.file, reuse_identities: dict[int, ifcopenshell.entity_instance]): + self.file = ifc_file + self.reuse_identities = reuse_identities + + def __enter__(self): + ifcopenshell.util.element.batch_remove_deep2(self.file) + + def __exit__(self, *args): + original_identities: dict[ifcopenshell.entity_instance, int] = {} + assert self.file.to_delete is not None + elements = self.file.to_delete + for identity, element in self.reuse_identities.items(): + if element in elements: + original_identities[element] = identity + assert len(original_identities) == len(elements) + + # Actually remove elements. + for element in self.file.to_delete: + if element in self.file.to_delete: + self.file.remove(element) + self.file.to_delete = None + + # Clean up dead identities. + for identity in original_identities.values(): + del self.reuse_identities[identity] + + class Usecase: file: ifcopenshell.file settings: dict[str, Any] @@ -246,7 +277,8 @@ class Usecase: matrix = ifcopenshell.util.placement.get_local_placement(placement) matrix = ifcopenshell.util.geolocation.auto_local2global(self.settings["library"], matrix) matrix = ifcopenshell.util.geolocation.auto_global2local(self.file, matrix) - ifcopenshell.api.geometry.edit_object_placement(self.file, element, matrix, is_si=False) + with SafeRemovalContext(self.file, self.reuse_identities): + ifcopenshell.api.geometry.edit_object_placement(self.file, element, matrix, is_si=False) element_type = ifcopenshell.util.element.get_type(self.settings["element"]) if element_type: @@ -388,8 +420,10 @@ class Usecase: equivalent_existing_context = self.create_equivalent_context(added_context) for inverse in self.file.get_inverse(added_context): ifcopenshell.util.element.replace_attribute(inverse, added_context, equivalent_existing_context) - for added_context in added_contexts: - ifcopenshell.util.element.remove_deep2(self.file, added_context) + + with SafeRemovalContext(self.file, self.reuse_identities): + for added_context in added_contexts: + ifcopenshell.util.element.remove_deep2(self.file, added_context) def get_equivalent_existing_context( self, added_context: ifcopenshell.entity_instance 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 c3ef86efcf..68dc7b92f5 100644 --- a/src/ifcopenshell-python/test/api/project/test_append_asset.py +++ b/src/ifcopenshell-python/test/api/project/test_append_asset.py @@ -459,6 +459,22 @@ class TestAppendAssetIFC2X3(test.bootstrap.IFC2X3): assert "Test" in pset_data assert ifcopenshell.util.element.get_psets(element2_) == pset_data + def test_reuse_identities_to_avoid_removed_entities_and_possible_crashes(self): + self.file.create_entity("IfcProject") + context = ifcopenshell.api.context.add_context(self.file, context_type="Model") + library = ifcopenshell.api.project.create_file(version=self.file.schema) + library.create_entity("IfcProject") + element = ifcopenshell.api.root.create_entity(library, ifc_class="IfcWall") + item = library.create_entity("IfcBoundingBox") + lib_context = ifcopenshell.api.context.add_context(library, context_type="Model") + representation = library.create_entity("IfcShapeRepresentation", Items=[item], ContextOfItems=lib_context) + element.Representation = library.create_entity("IfcProductDefinitionShape", Representations=[representation]) + reuse_identities: dict[int, ifcopenshell.entity_instance] = {} + ifcopenshell.api.project.append_asset( + self.file, library=library, element=element, reuse_identities=reuse_identities + ) + str(reuse_identities) # Will trigger crash if there are no removed entities. + def test_file_add_to_convert_units(self): library = ifcopenshell.file() builder = ShapeBuilder(library)