Move Blender-independent space generation algorithms from Bonsai
(GPL) to ifcopenshell.util (LGPL):
- ifcopenshell.util.shape.bisect_mesh_plane_vf: vectorized numpy
triangle/plane intersection for mesh bisection
- ifcopenshell.util.element.iter_top_connections: walker for
IfcRelConnectsElements(TOP) relationships
- ifcopenshell.util.space: new module with get_boundary_lines,
get_space_polygon, get_auto_space_height and height detection
helpers — all operating on IFC geometry without Blender
Bonsai's tool/spatial.py now delegates to these utilities via
thin wrappers, keeping only Blender-specific concerns (cache
management with depsgraph invalidation, UI property reads).
tool/wall.py iter_wall_slab_connections delegates to
ifcopenshell.util.element.iter_top_connections.
Added 22 tests: 6 for bisect_mesh_plane_vf, 10 for space
generation algorithms, 4 for iter_top_connections, 2 Bonsai
integration tests for cache behavior.
Generated with the assistance of an AI coding tool.
replace_attribute() rewrites references inside aggregate attributes via
element.walk(), but never checked whether the replacement value was
already present elsewhere in the same aggregate. For an EXPRESS SET
(e.g. IfcProject.RepresentationContexts, IfcRelAggregates.RelatedObjects)
this can leave the same reference listed twice, which is invalid IFC.
LIST and BAG aggregates legitimately allow duplicates, so a blanket dedup
would be wrong; only SET-typed attributes are deduplicated, determined at
runtime from the schema declaration (IfcOpenShell#8706 review comment).
The SET/LIST/BAG check is cached per (schema, class, attribute index), and
the dedup pass itself only runs when a cheap linear pre-check finds the
replacement value already present in the aggregate, so the common case
(no duplicate produced) pays only that pre-check, not a hash-set rebuild.
Benchmarked against a 23MB (431k entities) and a 104MB (2.4M entities) IFC
model against a large SET attribute: worst case adds well under 1ms per
call; the realistic case (merging duplicate contexts, matching the PR
#8706 scenario) shows no measurable regression.
Fixes the root cause flagged in IfcOpenShell#8706 (Moult), obviating the
need for MergeDuplicateContexts' own manual aggregate-dedup pass for that
scenario.
Generated with the assistance of an AI coding tool.
(cherry picked from commit 8c434b7167)