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 )