From 77d7d6dcdc88533713053d36cbf2c721b56d5e49 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sat, 11 Jul 2026 15:49:36 +0300 Subject: [PATCH] Bonsai: re-orthonormalize item placement in Item Mode (#6996) Entering and leaving Item Mode on an element with IfcExtrudedAreaSolid items corrupts their orientation, and the corruption persists after save and reopen. get_axis2placement builds the item frame using the placement's RefDirection as the X axis without projecting it onto the plane perpendicular to Axis. IFC permits a non perpendicular RefDirection (only its projection is significant), so a legal placement yields a sheared, non orthonormal matrix. import_item assigns that to the Blender item object's matrix_world, but a Blender object transform stores only location, rotation and scale and cannot represent shear, so the next depsgraph evaluation recomposes it into a wrong rotation. sync_item_positions then reads the corrupted matrix and writes it back into IfcExtrudedAreaSolid.Position, permanently corrupting the geometry. Re-orthonormalize the frame right after get_axis2placement (keep Z, project X onto the plane perpendicular to Z, recompute Y). This is the same projection the geometry kernel already applies, so rendered geometry is unchanged; it only removes the shear Blender cannot store. No-op when RefDirection is already perpendicular or absent, guarded against the degenerate parallel case. Verified live (headless Blender, enter+leave+save over 50 solids): the maximum change in the normalized extrusion axis goes from 1.026 (segments flipping, the bug) to 7e-6 (identity within float noise), and the items are no longer flagged as moved, so their Positions are left untouched. Generated with the assistance of an AI coding tool. Co-Authored-By: Claude Opus 4.8 --- src/bonsai/bonsai/tool/geometry.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index c4b7cd0ef6..546ae1a5b1 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -2248,6 +2248,23 @@ class Geometry(bonsai.core.tool.Geometry): if position or not is_swept_area: unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) position = ifcopenshell.util.placement.get_axis2placement(position) + # get_axis2placement uses the placement's RefDirection as the X axis as + # is, without projecting it onto the plane perpendicular to Axis. IFC + # allows RefDirection to be non perpendicular to Axis (only its + # projection matters), so such a legal placement yields a sheared, non + # orthonormal matrix. A Blender object transform can only store + # location, rotation and scale, so on the next depsgraph evaluation the + # shear is silently baked into a wrong rotation, which flips the item's + # orientation and, once written back by sync_item_positions, corrupts + # the saved IfcSweptAreaSolid. Re orthonormalise the frame (keeping Z, + # projecting X) so it matches the geometry kernel and survives Blender. + z_axis = position[:3, 2] + x_axis = position[:3, 0] - np.dot(position[:3, 0], z_axis) * z_axis + x_axis_length = np.linalg.norm(x_axis) + if x_axis_length > 1e-12: + x_axis /= x_axis_length + position[:3, 0] = x_axis + position[:3, 1] = np.cross(z_axis, x_axis) position[:, 3][0:3] *= unit_scale item_matrix = np.array(rep_obj.matrix_world.copy()) if cartesian_point_offset is not None: