Fix slab layer geometry for rotated and angled slabs

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.
This commit is contained in:
Ryan Schultz
2026-02-21 20:39:44 -06:00
parent 84b0a63221
commit d79770003a
2 changed files with 32 additions and 5 deletions
+11 -2
View File
@@ -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
+21 -3
View File
@@ -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
)