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 <noreply@anthropic.com>
This commit is contained in:
Petru Conduraru
2026-07-12 15:05:41 +03:00
parent 0b7e25a3ef
commit 58dc1f34ed
+14
View File
@@ -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: