From 58dc1f34ed0c4d1f893ea5dffbf33bbcfd9341ad Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sun, 12 Jul 2026 15:05:41 +0300 Subject: [PATCH] Bonsai: delete representation items nested in an IfcGeometricSet (#6807) Deleting a part (segment) of a dimension in item mode silently failed and the part reappeared when the representation reloaded. Multi-segment dimension annotations store their segments as Elements INSIDE an IfcGeometricCurveSet; tool.Geometry.remove_representation_item only recognized IfcShapeRepresentation and IfcBooleanResult inverses, so for a nested segment the item was never detached and remove_deep2 refused to purge it (an external inverse remained). The Blender object vanished, the IFC entity stayed, and the reload brought it back. Recognize IfcGeometricSet inverses: detach the item from Elements and let remove_deep2 purge it. If the item is the set's last element (Elements is SET [1:?]), remove the whole set recursively instead, which keeps the representation valid. This affects all GeometricSet-based annotations (dimensions, leaders, etc.), not just the reported model. Verified live in headless Blender on the reporter's model: deleting item #286487 of annotation #82782 left the entity in the file before the fix ("victim entity STILL EXISTS") and removes it after ("victim entity gone", Elements reduced, saved file verified clean with pure ifcopenshell); deleting both elements removes the entire curve set leaving a valid Items list. TestRemoveRepresentationItem + shape-aspect tests: 4 passed. Generated with the assistance of an AI coding tool. Co-Authored-By: Claude Opus 4.8 --- src/bonsai/bonsai/tool/geometry.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index c4b7cd0ef6..fe5670f5e9 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -1774,6 +1774,7 @@ class Geometry(bonsai.core.tool.Geometry): [consider_inverses.append(texture := t) for t in getattr(representation_item, "HasTextures", [])] representation = None + geometric_set = None boolean_results_to_remove: set[ifcopenshell.entity_instance] = set() for inverse in ifc_file.get_inverse(representation_item): if inverse.is_a("IfcShapeRepresentation"): @@ -1781,6 +1782,10 @@ class Geometry(bonsai.core.tool.Geometry): shape_aspects.append(inverse.OfShapeAspect[0]) else: representation = inverse + elif inverse.is_a("IfcGeometricSet"): + # E.g. a dimension segment (curve) nested inside an + # IfcGeometricCurveSet of an annotation representation. + geometric_set = inverse elif inverse.is_a("IfcBooleanResult"): if inverse.SecondOperand == representation_item: other_operand = inverse.FirstOperand @@ -1812,6 +1817,15 @@ class Geometry(bonsai.core.tool.Geometry): for shape_aspect in shape_aspects: cls.remove_representation_items_from_shape_aspect([representation_item], shape_aspect) + if geometric_set: + new_elements = tuple(e for e in geometric_set.Elements if e != representation_item) + if not new_elements: + # An IfcGeometricSet must keep at least one element, so + # removing the set's last element removes the set itself + # (which purges the element together with the set's subgraph). + cls.remove_representation_item(geometric_set, element) + return + geometric_set.Elements = new_elements if representation: new_items = tuple(set(representation.Items) - {representation_item}) if not new_items: