Self-heal inconsistent slab extrusion depth on import

In slice_layerset_mesh (loader.py), detect when an AXIS3 slab's stored
extrusion.Depth is inconsistent with the sum of its LayerThicknesses
(i.e. depth_scale != 1.0). This was caused by old Bonsai code in
change_thickness that incorrectly scaled extrusion.Depth by
1/cos(obj.rotation_euler.x) for ObjectPlacement-rotated slabs, making
the mesh 1.414× too tall for 45°-rotated slabs.

Two-part fix applied on first import of affected files:
1. Scale mesh vertices in Z (from the mesh bottom) by 1/depth_scale so
   the local Z span equals total_perp_thickness × unit_scale, giving
   the correct physical slab geometry immediately.
2. Write the corrected extrusion.Depth = total_perp_thickness /
   extrusion_vec.z back to the IFC data so future imports load the
   correct geometry without requiring this correction.

Files saved after this fix will open correctly with depth_scale = 1.0
and no vertex adjustment needed.
This commit is contained in:
Ryan Schultz
2026-02-22 07:45:42 -06:00
parent 7c738c15d9
commit 385d4aa409
+22 -5
View File
@@ -1090,11 +1090,12 @@ class Loader(bonsai.core.tool.Loader):
no = Vector([0.0, 0.0, 1.0])
co = Vector((0.0, 0.0, offset))
# Bisect planes are always horizontal (world Z) for AXIS3.
# For well-formed IFC data, the mesh local Z span equals total_perp_thickness
# (extrusion.Depth is always set to thickness / extrusion_vec.z so that
# extrusion.Depth × extrusion_vec.z = thickness). depth_scale is kept as
# a safety net for IFC files from other authoring tools where the extrusion
# depth may not exactly match the sum of LayerThicknesses.
# The mesh local Z span should equal total_perp_thickness × unit_scale:
# extrusion.Depth × extrusion_vec.z = total_perp_thickness
# If the IFC data is inconsistent (e.g. from old Bonsai code that incorrectly
# scaled extrusion.Depth for ObjectPlacement-rotated slabs), we self-heal:
# scale the mesh vertices to the correct Z span and fix the stored depth so
# future imports load correctly without this correction.
extrusion_vec = cls.get_extrusion_vector(element).normalized()
ifc_extrusion_depth = None
if body_rep := ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW"):
@@ -1107,6 +1108,22 @@ class Loader(bonsai.core.tool.Loader):
total_perp_thickness = sum(l.LayerThickness for l in layer_set.MaterialLayers)
if ifc_extrusion_depth and total_perp_thickness:
depth_scale = abs(extrusion_vec.z) * (ifc_extrusion_depth / total_perp_thickness)
if abs(depth_scale - 1.0) > 1e-6 and ifc_extrusion_depth and body_rep:
# Z_span / depth_scale == total_perp_thickness × unit_scale for any
# extrusion direction, so scaling from the mesh bottom is always correct.
min_z = min(v.co.z for v in bm.verts)
for v in bm.verts:
v.co.z = min_z + (v.co.z - min_z) / depth_scale
# Fix the IFC data so future imports don't require this correction.
extrusion_vec_z = abs(extrusion_vec.z)
correct_depth = total_perp_thickness / extrusion_vec_z if extrusion_vec_z > 1e-6 else total_perp_thickness
for item in ifcopenshell.util.representation.resolve_representation(body_rep).Items:
while item.is_a("IfcBooleanResult"):
item = item.FirstOperand
if item.is_a("IfcExtrudedAreaSolid"):
item.Depth = correct_depth
break
depth_scale = 1.0
elif usage.LayerSetDirection == "AXIS1":
co = Vector((0.0, 0.0, offset))
no = cls.get_extrusion_vector(element).normalized()