Compare commits

...

2 Commits

Author SHA1 Message Date
Ryan Schultz ae1c5444c3 Fix inverted layer order for flipped slab direction
When the mesh centroid check detects a direction mismatch
and flips `no`, also reverse the layer list so layer ordering
remains consistent with the reference line convention.

Generated with the assistance of an AI coding tool.
2026-03-24 08:45:26 -05:00
Ryan Schultz cd00b9994d 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.
2026-03-23 13:12:28 -05:00
4 changed files with 43 additions and 11 deletions
@@ -1898,14 +1898,23 @@ class CutDecorator:
no = tool.Drawing.get_extrusion_vector(element).normalized()
no = Vector([1.0, 0.0, 0.0])
no *= sense_factor
last_i = len(layer_set.MaterialLayers) - 1
# 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
layers = list(layer_set.MaterialLayers)
if (mesh_centroid - co).dot(no) < 0:
no = -no
layers = list(reversed(layers))
last_i = len(layers) - 1
vert_map = {}
verts = []
edges = []
j = 0
for i, layer in enumerate(layer_set.MaterialLayers):
for i, layer in enumerate(layers):
prev_co = co.copy()
co += no * layer.LayerThickness * self.unit_scale
if i != last_i:
@@ -729,8 +729,17 @@ class CreateDrawing(bpy.types.Operator):
no = tool.Drawing.get_extrusion_vector(element).normalized()
no = Vector([1.0, 0.0, 0.0])
no *= sense_factor
last_i = len(layer_set.MaterialLayers) - 1
for i, layer in enumerate(layer_set.MaterialLayers):
# 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
layers = list(layer_set.MaterialLayers)
if (mesh_centroid - co).dot(no) < 0:
no = -no
layers = list(reversed(layers))
last_i = len(layers) - 1
for i, layer in enumerate(layers):
prev_co = co.copy()
co += no * layer.LayerThickness * self.unit_scale
+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)
+11 -2
View File
@@ -1090,6 +1090,15 @@ 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.
layers = list(layer_set.MaterialLayers)
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
layers = list(reversed(layers))
# Cache this
body = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
styles = {}
@@ -1097,8 +1106,8 @@ class Loader(bonsai.core.tool.Loader):
for i, material in enumerate(mesh.materials):
if style := tool.Ifc.get_entity(material):
styles[style] = i
last_i = len(layer_set.MaterialLayers) - 1
for i, layer in enumerate(layer_set.MaterialLayers):
last_i = len(layers) - 1
for i, layer in enumerate(layers):
if i != last_i:
prev_co = co.copy()
co += no * layer.LayerThickness * cls.unit_scale