Fix #7208. Merge duplicates patch recipe can now control merging by empty attributes.

This commit is contained in:
Dion Moult
2025-10-06 23:18:27 +11:00
parent 7cba63af9c
commit 25dacc8fed
2 changed files with 18 additions and 4 deletions
@@ -25,7 +25,7 @@ from typing import Any
class Patcher: class Patcher:
def __init__(self, file: ifcopenshell.file, logger: Logger, attribute: str = "Tag"): def __init__(self, file: ifcopenshell.file, logger: Logger, attribute: str = "Tag", should_merge_null: bool = True):
"""Merge duplicate element types via the Tag or another attribute """Merge duplicate element types via the Tag or another attribute
Revit is notorious for creating many duplicate element types. Element Revit is notorious for creating many duplicate element types. Element
@@ -50,6 +50,8 @@ class Patcher:
:param attribute: The name of the attribute to merge element types based :param attribute: The name of the attribute to merge element types based
on. Typically this will be "Tag" as it stores the unique ID from the on. Typically this will be "Tag" as it stores the unique ID from the
proprietary BIM software. proprietary BIM software.
:param should_merge_null: If True, all elements with an empty attribute
will be merged. If False, they will be kept separate.
Example: Example:
@@ -64,12 +66,15 @@ class Patcher:
self.file = file self.file = file
self.logger = logger self.logger = logger
self.attribute = attribute self.attribute = attribute
self.should_merge_null = should_merge_null
def patch(self): def patch(self):
key = self.attribute
keys: dict[Any, ifcopenshell.entity_instance] = {} keys: dict[Any, ifcopenshell.entity_instance] = {}
for element_type in self.file.by_type("IfcTypeObject"): for element_type in self.file.by_type("IfcTypeObject"):
original_type = keys.get(getattr(element_type, key), None) key = getattr(element_type, self.attribute)
if not key and not self.should_merge_null:
continue
original_type = keys.get(key, None)
if original_type: if original_type:
elements = ifcopenshell.util.element.get_types(element_type) elements = ifcopenshell.util.element.get_types(element_type)
if elements: if elements:
@@ -78,7 +83,7 @@ class Patcher:
ifcopenshell.util.element.replace_attribute(inverse, element_type, original_type) ifcopenshell.util.element.replace_attribute(inverse, element_type, original_type)
self.file.remove(element_type) self.file.remove(element_type)
else: else:
keys[getattr(element_type, key)] = element_type keys[key] = element_type
def assign_type( def assign_type(
self, related_objects: list[ifcopenshell.entity_instance], relating_type: ifcopenshell.entity_instance self, related_objects: list[ifcopenshell.entity_instance], relating_type: ifcopenshell.entity_instance
@@ -74,6 +74,15 @@ class TestMergeDuplicateTypes(test.bootstrap.IFC4):
assert ifcopenshell.util.element.get_material(wall1, should_inherit=False) == None assert ifcopenshell.util.element.get_material(wall1, should_inherit=False) == None
assert ifcopenshell.util.element.get_material(wall2, should_inherit=False) == None assert ifcopenshell.util.element.get_material(wall2, should_inherit=False) == None
def test_not_merging_empty_attributes(self):
wall_type1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType", name="")
wall_type2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType", name="")
output = ifcpatch.execute({"file": self.file, "recipe": "MergeDuplicateTypes", "arguments": ["Name", True]})
assert len(output.by_type("IfcWallType")) == 1
wall_type3 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType", name="")
output = ifcpatch.execute({"file": self.file, "recipe": "MergeDuplicateTypes", "arguments": ["Name", False]})
assert len(output.by_type("IfcWallType")) == 2
class TestMergeDuplicateTypesIFC2X3(test.bootstrap.IFC2X3, TestMergeDuplicateTypes): class TestMergeDuplicateTypesIFC2X3(test.bootstrap.IFC2X3, TestMergeDuplicateTypes):
pass pass