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.
This commit is contained in:
Ryan Schultz
2026-03-23 13:12:28 -05:00
parent c026dd3b6e
commit cd00b9994d
4 changed files with 31 additions and 5 deletions
@@ -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 = {}
@@ -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()
+10 -5
View File
@@ -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)
+7
View File
@@ -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 = {}