From cd00b9994d9e258a4f4ad63a5456c692fe3911dc Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Mon, 23 Mar 2026 13:12:28 -0500 Subject: [PATCH] Fix #7838: Fix slab layer direction and thickness offset For IFC files exported from Revit, IfcMaterialLayerSetUsage may declare DirectionSense=POSITIVE while the IfcExtrudedAreaSolid extrudes in the negative direction. The layer slicing planes in loader.py, decoration.py, and operator.py now detect this by checking whether the mesh centroid is behind the starting plane, and flip the normal accordingly. The change_thickness function in slab.py was also overwriting the full extrusion position coordinates, discarding the XY profile origin encoded by Revit. It now decomposes the original position into components parallel and perpendicular to the layer normal, preserving the perpendicular (XY) component. Generated with the assistance of an AI coding tool. --- .../bonsai/bim/module/drawing/decoration.py | 7 +++++++ src/bonsai/bonsai/bim/module/drawing/operator.py | 7 +++++++ src/bonsai/bonsai/bim/module/model/slab.py | 15 ++++++++++----- src/bonsai/bonsai/tool/loader.py | 7 +++++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/decoration.py b/src/bonsai/bonsai/bim/module/drawing/decoration.py index b9b4dd40cb..4aed1073d7 100644 --- a/src/bonsai/bonsai/bim/module/drawing/decoration.py +++ b/src/bonsai/bonsai/bim/module/drawing/decoration.py @@ -1898,6 +1898,13 @@ class CutDecorator: no = tool.Drawing.get_extrusion_vector(element).normalized() no = Vector([1.0, 0.0, 0.0]) no *= sense_factor + # Detect non-conformant exports (e.g. Revit) where DirectionSense=POSITIVE + # but the geometry extrudes in the negative direction. If the mesh centroid + # in object local space is on the wrong side of the starting plane, flip no. + bb = [Vector(v) for v in obj.bound_box] + mesh_centroid = sum(bb, Vector((0.0, 0.0, 0.0))) / 8 + if (mesh_centroid - co).dot(no) < 0: + no = -no last_i = len(layer_set.MaterialLayers) - 1 vert_map = {} diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 58474d8ce5..35a933c406 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -729,6 +729,13 @@ class CreateDrawing(bpy.types.Operator): no = tool.Drawing.get_extrusion_vector(element).normalized() no = Vector([1.0, 0.0, 0.0]) no *= sense_factor + # Detect non-conformant exports (e.g. Revit) where DirectionSense=POSITIVE + # but the geometry extrudes in the negative direction. If the mesh centroid + # in object local space is on the wrong side of the starting plane, flip no. + bb = [Vector(v) for v in obj.bound_box] + mesh_centroid = sum(bb, Vector((0.0, 0.0, 0.0))) / 8 + if (mesh_centroid - co).dot(no) < 0: + no = -no last_i = len(layer_set.MaterialLayers) - 1 for i, layer in enumerate(layer_set.MaterialLayers): prev_co = co.copy() diff --git a/src/bonsai/bonsai/bim/module/model/slab.py b/src/bonsai/bonsai/bim/module/model/slab.py index 58a353ab28..c28ba920b7 100644 --- a/src/bonsai/bonsai/bim/module/model/slab.py +++ b/src/bonsai/bonsai/bim/module/model/slab.py @@ -248,7 +248,8 @@ class DumbSlabPlaner: def change_thickness(self, element: ifcopenshell.entity_instance, thickness: float) -> None: self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - if tool.Model.get_usage_type(element) != "LAYER3": + usage_type = tool.Model.get_usage_type(element) + if usage_type != "LAYER3": return layer_params = tool.Model.get_material_layer_parameters(element) ifc_file = tool.Ifc.get() @@ -304,11 +305,15 @@ class DumbSlabPlaner: ifc_position = extrusion.Position position = offset_direction * perpendicular_offset material = ifcopenshell.util.element.get_material(element) - if material: - if material.is_a("IfcMaterialLayerSetUsage"): - material.OffsetFromReferenceLine = position.z if ifc_position: - ifc_position.Location.Coordinates = position + orig = Vector(ifc_position.Location.Coordinates) + layer_normal = offset_direction.normalized() + perp = orig - orig.dot(layer_normal) * layer_normal + new_coords = perp + layer_normal * perpendicular_offset + ifc_position.Location.Coordinates = new_coords + if material: + if material.is_a("IfcMaterialLayerSetUsage"): + material.OffsetFromReferenceLine = new_coords.z else: tool.Model.add_extrusion_position(extrusion, position) diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index 6d30192485..b6167cf993 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -1090,6 +1090,13 @@ class Loader(bonsai.core.tool.Loader): no = cls.get_extrusion_vector(element).normalized() no = Vector([1.0, 0.0, 0.0]) no *= sense_factor + # Detect non-conformant exports (e.g. Revit) where DirectionSense=POSITIVE + # but the geometry extrudes in the negative direction. If the mesh centroid + # in object local space is on the wrong side of the starting plane, flip no. + if bm.verts: + mesh_centroid = sum((v.co for v in bm.verts), Vector((0.0, 0.0, 0.0))) / len(bm.verts) + if (mesh_centroid - co).dot(no) < 0: + no = -no # Cache this body = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") styles = {}