From d79770003ad72a317cdc331ef87ad7c8c397b091 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Sat, 21 Feb 2026 20:39:44 -0600 Subject: [PATCH] Fix slab layer geometry for rotated and angled slabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs fixed: 1. EditAssignedMaterial was sweeping all slabs that share a layer set when changing material properties, instead of updating only the selected element's assigned material. 2. slice_layerset_mesh (loader.py): layer bisect positions were scaled incorrectly due to a stale unit_scale and a reversed/missing DirectionSense guard. Now always recalculates unit_scale fresh and correctly applies sense_factor. 3. change_thickness (slab.py): extrusion depth was computed as `thickness / cos(obj.rotation_euler.x)`, which incorrectly scaled ObjectPlacement-rotated slabs (where the extrusion direction is local Z and no scaling is needed). The correct formula is `thickness / extrusion_vec.z`, which handles both ObjectPlacement rotation (extrusion_vec.z ≈ 1.0 → no scale) and ExtrudedDirection tilts (extrusion_vec.z < 1.0 → scale up) uniformly. The resulting slab was 1.414× too thick for 45°-rotated slabs, making both material layers appear fatter than specified. slice_layerset_mesh retains a depth_scale safety factor (extrusion_vec.z × ifc_depth / total_layer_thickness) as a robustness guard for IFC files from other authoring tools where extrusion depth may not match the sum of LayerThicknesses. --- src/bonsai/bonsai/bim/module/model/slab.py | 13 ++++++++++-- src/bonsai/bonsai/tool/loader.py | 24 +++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/slab.py b/src/bonsai/bonsai/bim/module/model/slab.py index acfffb7c1c..19a4adfa20 100644 --- a/src/bonsai/bonsai/bim/module/model/slab.py +++ b/src/bonsai/bonsai/bim/module/model/slab.py @@ -281,8 +281,17 @@ class DumbSlabPlaner: existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, 2 * pi, tolerance=0.001) else existing_x_angle direction_ratios = Vector(extrusion.ExtrudedDirection.DirectionRatios) offset_direction = direction_ratios.copy() - perpendicular_depth = thickness * abs(1 / cos(existing_x_angle)) - perpendicular_offset = layer_offset * abs(1 / cos(existing_x_angle)) / self.unit_scale + # The extrusion depth needed to achieve a given perpendicular thickness depends on + # how much the extrusion direction deviates from the slab face normal (local Z). + # For an ObjectPlacement-rotated slab, extrusion_vec.z ≈ 1.0 → no scaling. + # For an ExtrudedDirection-tilted slab, extrusion_vec.z < 1.0 → scale up. + extrusion_z = abs(direction_ratios.normalized().z) + if extrusion_z > 1e-6: + perpendicular_depth = thickness / extrusion_z + perpendicular_offset = layer_offset / extrusion_z / self.unit_scale + else: + perpendicular_depth = thickness + perpendicular_offset = layer_offset / self.unit_scale ifc_position = extrusion.Position diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index 24b55bcf03..56f22a97b9 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -1086,6 +1086,7 @@ class Loader(bonsai.core.tool.Loader): bm = bmesh.new() bm.from_mesh(mesh) prev_co = None + depth_scale = 1.0 if not usage: sense_factor = 1 # Assume the extrusion vector points in the direction sense no = cls.get_extrusion_vector(element).normalized() @@ -1095,9 +1096,26 @@ class Loader(bonsai.core.tool.Loader): no = cls.get_extrusion_vector(element).normalized() no = no.cross(Vector([1.0, 0.0, 0.0])) elif usage.LayerSetDirection == "AXIS3": - co = Vector((0.0, 0.0, offset)) - no = cls.get_extrusion_vector(element).normalized() 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. + 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"): + for item in ifcopenshell.util.representation.resolve_representation(body_rep).Items: + while item.is_a("IfcBooleanResult"): + item = item.FirstOperand + if item.is_a("IfcExtrudedAreaSolid"): + ifc_extrusion_depth = item.Depth + break + 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) elif usage.LayerSetDirection == "AXIS1": co = Vector((0.0, 0.0, offset)) no = cls.get_extrusion_vector(element).normalized() @@ -1114,7 +1132,7 @@ class Loader(bonsai.core.tool.Loader): for i, layer in enumerate(layer_set.MaterialLayers): if i != last_i: prev_co = co.copy() - co += no * layer.LayerThickness * unit_scale + co += no * layer.LayerThickness * depth_scale * unit_scale bisect_geom = bmesh.ops.bisect_plane( bm, geom=bm.verts[:] + bm.edges[:] + bm.faces[:], dist=0.0001, plane_co=co, plane_no=no )