mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 17:11:02 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -2248,6 +2248,23 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
if position or not is_swept_area:
|
if position or not is_swept_area:
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
position = ifcopenshell.util.placement.get_axis2placement(position)
|
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
|
position[:, 3][0:3] *= unit_scale
|
||||||
item_matrix = np.array(rep_obj.matrix_world.copy())
|
item_matrix = np.array(rep_obj.matrix_world.copy())
|
||||||
if cartesian_point_offset is not None:
|
if cartesian_point_offset is not None:
|
||||||
|
|||||||
Reference in New Issue
Block a user