diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py index 79eb418099..c5b4808f98 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py @@ -206,14 +206,17 @@ class Usecase: self.target_class = "IfcPresentationStyle" return self.append_presentation_style() + def by_guid(self, guid: str) -> Union[ifcopenshell.entity_instance, None]: + try: + return self.file.by_guid(guid) + except RuntimeError: + return None + def get_existing_element(self, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: if element.id() in self.added_elements: return self.added_elements[element.id()] if element.is_a("IfcRoot"): - try: - return self.file.by_guid(element.GlobalId) - except RuntimeError: - return None + return self.by_guid(element.GlobalId) elif not self.assume_asset_uniqueness_by_name: return None elif element.is_a("IfcMaterial"): @@ -364,10 +367,13 @@ class Usecase: # Check if inverse element was created before. # Still need to recreate it again - e.g. it could be some rel # that now needs it's RelatingObjects to be extended by the current asset. + existing_rel = None if (new := self.reuse_identities.get(element_identity)) is not None: # Currently known cases requiring attributes reassignment are rels. if not new.is_a("IfcRelationship"): return + elif element.is_a("IfcRelationship") and (existing_rel := self.by_guid(element.GlobalId)): + new = existing_rel else: new = self.file.create_entity(element.is_a()) self.reuse_identities[element_identity] = new @@ -382,6 +388,11 @@ class Usecase: for item in attribute: if not self.is_another_asset(item): new_attribute.append(self.add_element(item)) + # If rel exists we need to make sure previously assigned elements are untouched + # e.g. not to assign a material or a pset from element. + if existing_rel: + new_attribute.extend(existing_rel[i]) + new_attribute = list(set(new_attribute)) else: new_attribute = attribute if new_attribute is not None: @@ -389,19 +400,12 @@ class Usecase: def is_another_asset(self, element: ifcopenshell.entity_instance) -> bool: """Is IFC entity from inverse attribute is another asset to append that should be skipped.""" - - def by_guid(guid: str) -> Union[ifcopenshell.entity_instance, None]: - try: - return self.file.by_guid(guid) - except RuntimeError: - return None - if element == self.settings["element"]: return False elif element.is_a("IfcFeatureElement"): # Feature elements match the target class but aren't considered "assets" return False - elif element.is_a("IfcRoot") and by_guid(element.GlobalId) is not None: + elif element.is_a("IfcRoot") and self.by_guid(element.GlobalId) is not None: return False elif element.is_a(self.target_class): return True 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 30f4322199..cee115df4c 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,24 @@ class TestAppendAssetIFC2X3(test.bootstrap.IFC2X3): assert "Test" in pset_data assert ifcopenshell.util.element.get_psets(element2_) == pset_data + def test_update_rels_appending_subsequent_assets_identifying_rels_by_guids(self): + library = ifcopenshell.api.project.create_file(version=self.file.schema) + element1 = ifcopenshell.api.root.create_entity(library, ifc_class="IfcWall") + pset = ifcopenshell.api.pset.add_pset(library, product=element1, name="Test") + element2 = ifcopenshell.api.root.create_entity(library, ifc_class="IfcWall") + pset_rel = ifcopenshell.api.pset.assign_pset(library, pset=pset, products=[element2]) + assert pset_rel + pset_rel_guid: str = pset_rel.GlobalId + + element1_ = ifcopenshell.api.project.append_asset(self.file, library, element1) + element2_ = ifcopenshell.api.project.append_asset(self.file, library, element2) + pset_data = ifcopenshell.util.element.get_psets(element1_) + + appended_rels = [e for e in self.file.by_type("IfcRelDefinesByProperties") if e.GlobalId == pset_rel_guid] + assert len(appended_rels) == 1 + assert set(appended_rels[0].RelatedObjects) == {element1_, element2_} + 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")