From 385d4aa40935bbebf47cd05574152aa323f5176a Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Sun, 22 Feb 2026 07:45:42 -0600 Subject: [PATCH] Self-heal inconsistent slab extrusion depth on import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/bonsai/bonsai/tool/loader.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index eae2f35c52..4758bcefd3 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -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()