Fix add_boolean removing second operands from unrelated representations

add_boolean was removing second operands from ALL IfcShapeRepresentations
that referenced them, which could corrupt unrelated shapes and leave
representations with empty Items (bug #7803).

The API no longer modifies Items — callers manage this explicitly.
validate_type and Bonsai's AddBoolean operator now handle their own
item removal scoped to the correct representation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-03-21 18:21:41 +11:00
parent bcfad8d96d
commit fca258fb07
5 changed files with 38 additions and 37 deletions
@@ -31,17 +31,6 @@ def add_boolean(
) -> list[ifcopenshell.entity_instance]:
"""Adds a boolean operation to two or more representation items
If an IfcBooleanOperand is part of the top level items in an
IfcShapeRepresentation, it will be removed from that level whilst being
added to the IfcBooleanResult. This is because it is generally intuitive
that an item is either participating in a boolean operation, or being an
item in its own right, but not both.
However, if an IfcBooleanOperand is part of another boolean operation
already, it will not be removed from the existing operation. A new
operation will be created, and therefore it will participate in two
operations.
This function protects against recursive booleans.
After a boolean operation is made, since the items of
@@ -101,9 +90,6 @@ def add_boolean(
booleans = []
for second_item in second_items:
for inverse in file.get_inverse(second_item):
if inverse.is_a("IfcShapeRepresentation"):
inverse.Items = list(set(inverse.Items) - {second_item})
if first.is_a("IfcTesselatedFaceSet"):
first.Closed = True # For now, trust the user to do the right thing.
if second_item.is_a("IfcTesselatedFaceSet"):
@@ -83,6 +83,7 @@ def validate_type(
if remaining_items:
ifcopenshell.api.geometry.add_boolean(file, preferred_item, remaining_items, "UNION")
representation.Items = [i for i in representation.Items if i not in remaining_items]
representation.RepresentationType = ifcopenshell.util.representation.guess_type(representation.Items)
if representation.RepresentationType == "CSG":