append_asset to use custom file.add to prevent duplicating profiles and materials #5925

This commit is contained in:
Andrej730
2025-01-20 16:05:13 +05:00
parent 0a266bba5d
commit 3ffc0bc39c
2 changed files with 90 additions and 2 deletions
@@ -143,6 +143,7 @@ class Usecase:
file: ifcopenshell.file file: ifcopenshell.file
settings: dict[str, Any] settings: dict[str, Any]
assume_asset_uniqueness_by_name: bool assume_asset_uniqueness_by_name: bool
whitelisted_inverse_attributes: dict[str, list[str]]
def execute(self): def execute(self):
# mapping of old element ids to new elements # mapping of old element ids to new elements
@@ -268,7 +269,7 @@ class Usecase:
existing_element = self.get_existing_element(element) existing_element = self.get_existing_element(element)
if existing_element: if existing_element:
return existing_element return existing_element
new = self.file.add(element) new = self.file_add(element)
self.added_elements[element.id()] = new self.added_elements[element.id()] = new
self.check_inverses(element) self.check_inverses(element)
subelement_queue = self.settings["library"].traverse(element, max_levels=1)[1:] subelement_queue = self.settings["library"].traverse(element, max_levels=1)[1:]
@@ -280,7 +281,7 @@ class Usecase:
if not self.has_whitelisted_inverses(existing_element): if not self.has_whitelisted_inverses(existing_element):
self.check_inverses(subelement) self.check_inverses(subelement)
else: else:
self.added_elements[subelement.id()] = self.file.add(subelement) self.added_elements[subelement.id()] = self.file_add(subelement)
self.check_inverses(subelement) self.check_inverses(subelement)
subelement_queue.extend(self.settings["library"].traverse(subelement, max_levels=1)[1:]) subelement_queue.extend(self.settings["library"].traverse(subelement, max_levels=1)[1:])
return new return new
@@ -421,3 +422,56 @@ class Usecase:
context_type=added_context.ContextType, context_type=added_context.ContextType,
context_identifier=added_context.ContextIdentifier, context_identifier=added_context.ContextIdentifier,
) )
def file_add(self, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
"""Reimplementation of `file.add` but taking into account that some elements (profiles, materials)
are already existing (checking by their name) and shouldn't be duplicated.
The problem with `file.add` it's recursively adding element and all it's attributes
and there is no control to prevent it from adding certain type of elements.
"""
ifc_file = self.file
if not self.assume_asset_uniqueness_by_name:
return ifc_file.add(element)
reuse_identities = self.reuse_identities
element_identity = element.wrapped_data.identity()
if added_element := reuse_identities.get(element_identity):
return added_element
# Maybe element already exists.
if element.is_a("IfcProfileDef"):
profile_name = element.ProfileName
existing_profile = next(
(e for e in ifc_file.by_type("IfcProfileDef") if e.ProfileName == profile_name), None
)
if existing_profile is not None:
reuse_identities[element_identity] = existing_profile
return existing_profile
elif element.is_a("IfcMaterial"):
material_name = element.Name
existing_material = next((e for e in ifc_file.by_type("IfcMaterial") if e.Name == material_name), None)
if existing_material is not None:
reuse_identities[element_identity] = existing_material
return existing_material
attrs = {}
for attr_index, attr_value in enumerate(element):
# `None` is set by default already.
if attr_value is None:
continue
elif isinstance(attr_value, ifcopenshell.entity_instance):
attr_value = self.file_add(attr_value)
elif isinstance(attr_value, tuple):
# Assume type is consistent across the tuple.
if isinstance(attr_value[0], ifcopenshell.entity_instance):
attr_value = tuple(self.file_add(e) for e in attr_value)
attrs[attr_index] = attr_value
# Adding entity at the end just to keep it consistent with `file.add`.
new = ifc_file.create_entity(element.is_a())
reuse_identities[element_identity] = new
for attr_index, attr_value in attrs.items():
new[attr_index] = attr_value
return new
@@ -28,6 +28,7 @@ import ifcopenshell.api.style
import ifcopenshell.api.context import ifcopenshell.api.context
import ifcopenshell.api.project import ifcopenshell.api.project
import ifcopenshell.api.material import ifcopenshell.api.material
import ifcopenshell.api.profile
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
import numpy as np import numpy as np
@@ -499,3 +500,36 @@ class TestAppendAssetIFC4(test.bootstrap.IFC4, TestAppendAssetIFC2X3):
appended_item = self.file.by_type("IfcCostSchedule")[0].Controls[0].RelatedObjects[0] appended_item = self.file.by_type("IfcCostSchedule")[0].Controls[0].RelatedObjects[0]
assert appended_item.is_a("IfcCostItem") assert appended_item.is_a("IfcCostItem")
assert appended_item.IsNestedBy[0].RelatedObjects[0].is_a("IfcCostItem") assert appended_item.IsNestedBy[0].RelatedObjects[0].is_a("IfcCostItem")
def test_not_duplicate_profiles_and_materials_based_on_name(self):
library = ifcopenshell.api.project.create_file(version=self.file.schema)
column_type = ifcopenshell.api.root.create_entity(library, "IfcColumnType")
library_profile = ifcopenshell.api.profile.add_parameterized_profile(library, "IfcCircleProfileDef")
library_profile.ProfileName = "TestProfile"
material_set = ifcopenshell.api.material.add_material_set(library, set_type="IfcMaterialProfileSet")
material = ifcopenshell.api.material.add_material(library, "TestMaterial")
ifcopenshell.api.material.add_profile(library, material_set, material, library_profile)
ifcopenshell.api.material.assign_material(
library, [column_type], material=material_set, type="IfcMaterialProfileSet"
)
# Test adding a profile with existing name.
profile = ifcopenshell.api.profile.add_parameterized_profile(self.file, "IfcCircleProfileDef")
profile.ProfileName = "TestProfile"
ifcopenshell.api.project.append_asset(self.file, library, library_profile)
profiles = self.file.by_type("IfcProfileDef")
assert len(profiles) == 1 and profiles[0].ProfileName == "TestProfile"
# Test adding a material with existing name.
material = ifcopenshell.api.material.add_material(self.file, "TestMaterial")
ifcopenshell.api.project.append_asset(self.file, library, library_profile)
materials = self.file.by_type("IfcMaterial")
assert len(materials) == 1 and materials[0].Name == "TestMaterial"
# Test implicitly adding profile+material with existing names.
ifcopenshell.api.project.append_asset(self.file, library, column_type)
assert len(self.file.by_type("IfcColumnType")) == 1
profiles = self.file.by_type("IfcProfileDef")
assert len(profiles) == 1 and profiles[0].ProfileName == "TestProfile"
materials = self.file.by_type("IfcMaterial")
assert len(materials) == 1 and materials[0].Name == "TestMaterial"