append_asset - extend existing rels matching them by project guid

Mentioned in #6039
This commit is contained in:
Andrej730
2025-01-31 16:24:05 +05:00
parent 089f15a5cc
commit c2ae3dcb08
2 changed files with 34 additions and 12 deletions
@@ -206,14 +206,17 @@ class Usecase:
self.target_class = "IfcPresentationStyle" self.target_class = "IfcPresentationStyle"
return self.append_presentation_style() 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]: def get_existing_element(self, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
if element.id() in self.added_elements: if element.id() in self.added_elements:
return self.added_elements[element.id()] return self.added_elements[element.id()]
if element.is_a("IfcRoot"): if element.is_a("IfcRoot"):
try: return self.by_guid(element.GlobalId)
return self.file.by_guid(element.GlobalId)
except RuntimeError:
return None
elif not self.assume_asset_uniqueness_by_name: elif not self.assume_asset_uniqueness_by_name:
return None return None
elif element.is_a("IfcMaterial"): elif element.is_a("IfcMaterial"):
@@ -364,10 +367,13 @@ class Usecase:
# Check if inverse element was created before. # Check if inverse element was created before.
# Still need to recreate it again - e.g. it could be some rel # 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. # 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: if (new := self.reuse_identities.get(element_identity)) is not None:
# Currently known cases requiring attributes reassignment are rels. # Currently known cases requiring attributes reassignment are rels.
if not new.is_a("IfcRelationship"): if not new.is_a("IfcRelationship"):
return return
elif element.is_a("IfcRelationship") and (existing_rel := self.by_guid(element.GlobalId)):
new = existing_rel
else: else:
new = self.file.create_entity(element.is_a()) new = self.file.create_entity(element.is_a())
self.reuse_identities[element_identity] = new self.reuse_identities[element_identity] = new
@@ -382,6 +388,11 @@ class Usecase:
for item in attribute: for item in attribute:
if not self.is_another_asset(item): if not self.is_another_asset(item):
new_attribute.append(self.add_element(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: else:
new_attribute = attribute new_attribute = attribute
if new_attribute is not None: if new_attribute is not None:
@@ -389,19 +400,12 @@ class Usecase:
def is_another_asset(self, element: ifcopenshell.entity_instance) -> bool: 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.""" """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"]: if element == self.settings["element"]:
return False return False
elif element.is_a("IfcFeatureElement"): elif element.is_a("IfcFeatureElement"):
# Feature elements match the target class but aren't considered "assets" # Feature elements match the target class but aren't considered "assets"
return False 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 return False
elif element.is_a(self.target_class): elif element.is_a(self.target_class):
return True return True
@@ -459,6 +459,24 @@ class TestAppendAssetIFC2X3(test.bootstrap.IFC2X3):
assert "Test" in pset_data assert "Test" in pset_data
assert ifcopenshell.util.element.get_psets(element2_) == 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): def test_reuse_identities_to_avoid_removed_entities_and_possible_crashes(self):
self.file.create_entity("IfcProject") self.file.create_entity("IfcProject")
context = ifcopenshell.api.context.add_context(self.file, context_type="Model") context = ifcopenshell.api.context.add_context(self.file, context_type="Model")