Skip to content

ifcpatch MergeDuplicateTypes: default merges distinct untagged types (data loss) and can crash on cross-class merges #8609

Description

@theoryshaw

Summary

The ifcpatch recipe MergeDuplicateTypes can silently destroy distinct element types (and, as a knock-on effect, break Bonsai array modifiers that reference them). There are two related problems in the recipe:

  1. should_merge_null=True is the default, which merges every type that has an empty merge attribute into a single type — even when those types are clearly not duplicates.
  2. Types are grouped by the attribute value only, ignoring their IFC class, so two unrelated types that happen to share a value can be merged, which then raises a TypeError from assign_type (or, worse, silently mis-types occurrences).

Recipe: src/ifcpatch/ifcpatch/recipes/MergeDuplicateTypes.py

Problem 1 — should_merge_null=True destroys distinct untagged types

The default groups types by Tag. With should_merge_null=True, all types with an empty Tag are treated as duplicates of one another and collapsed into a single type. But an empty attribute is an absence of evidence, not proof of duplication.

On a Bonsai-authored (or mixed) model this is destructive. For example a masonry assembly built with array modifiers had three distinct, differently-named types that all happened to lack a Tag:

  • IfcBuildingElementProxyType "Wire Ladder"
  • IfcBuildingElementProxyType "CMU Core"
  • IfcBuildingElementProxyType "Mortar Joint"

Running the default patch collapsed all three into one and removed the other two. Because these types were referenced as array children in BBIM_Array psets, re-opening the patched file in Bonsai produced:

setup_arrays: array parent 3QecqJiErB$9TeAgOjclzW references missing child GUID '2N9VxgdQL2eO0eoCEe8Bi_'.
setup_arrays: array parent 0QwQTUhPr6EAqfEjNat8se references missing child GUID '0d1KDqYRrCE9_7zIFzbh7M'.
setup_arrays: array parent 3iGRJZfy51iAd8K3LgiYDq references missing child GUID '3FI_7o4Dz1keW3geVnqXHX'.

("Arrays With Missing Children" warning in the Project panel.)

Genuine Revit duplicates — the recipe's intended target — always carry a populated Tag, so merging untagged types buys nothing for that workflow while risking data loss elsewhere.

Minimal repro

import ifcopenshell, ifcopenshell.api.root, ifcpatch

f = ifcopenshell.file(schema="IFC4")
for name in ("Wire Ladder", "CMU Core", "Mortar Joint"):
    ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingElementProxyType", name=name)
    # Tag left as None

out = ifcpatch.execute({"file": f, "recipe": "MergeDuplicateTypes", "arguments": []})
print(len(out.by_type("IfcBuildingElementProxyType")))  # -> 1 (expected 3)

Problem 2 — cross-class merge raises TypeError

Because the grouping key ignores the IFC class, two unrelated types with the same (or both-empty) attribute value can be merged. When the "keeper" type cannot type the other type's occurrences, assign_type raises. Observed on a Revit export:

TypeError: IfcTypeProduct cannot type IfcBeam in schema IFC4 (allowed occurrence classes: IfcAnnotation)

(Bonsai uses IfcTypeProduct for annotation types; it collided with a beam type on an empty Tag.)

Minimal repro

import ifcopenshell, ifcopenshell.api.root, ifcopenshell.api.type, ifcpatch

f = ifcopenshell.file(schema="IFC4")
at = ifcopenshell.api.root.create_entity(f, ifc_class="IfcTypeProduct")
a = ifcopenshell.api.root.create_entity(f, ifc_class="IfcAnnotation")
ifcopenshell.api.type.assign_type(f, related_objects=[a], relating_type=at, should_map_representations=False)
bt = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBeamType")
b = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBeam")
ifcopenshell.api.type.assign_type(f, related_objects=[b], relating_type=bt, should_map_representations=False)

ifcpatch.execute({"file": f, "recipe": "MergeDuplicateTypes", "arguments": ["Tag", True]})  # -> TypeError

Proposed fix

  1. Include the IFC class in the grouping key so only same-class types are ever merged.
  2. Change the default to should_merge_null=False, so untagged types are kept separate unless the caller explicitly opts in via ["Tag", True].

Both changes are small and localized to the recipe. Happy to open a PR with these plus regression tests covering: same-class populated-attribute duplicates still merge; distinct null-attribute types are preserved by default; cross-class types are never merged (no TypeError); and the explicit should_merge_null toggle still works.

Remaining edge case (not addressed by the above)

Even with those fixes, any type removal in this recipe can orphan a BBIM_Array "children" reference if a genuinely-duplicate type is also an array child. A more complete fix would make the recipe array-aware (repoint or clean up BBIM_Array child GUIDs when a referenced type is merged away). Noting it here for completeness.

Environment

  • IfcOpenShell / Bonsai v0.8.0 branch
  • Blender 4.5, Windows 10

Metadata

Metadata

Assignees

No one assigned

    Labels

    PR ProposedA PR has been proposed for this bug/feature.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions