mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Fix slab layer geometry: custom offset isolation, unit scale, and layer ordering
Three related bugs fixed in the slab material layer set workflow: 1. EditAssignedMaterial (operator.py): Applying a custom offset to one slab instance incorrectly regenerated geometry for ALL slabs sharing the same IfcMaterialLayerSet. Replaced regenerate_from_layer_set (sweeps all users) with per-element regenerate_from_occurence for AXIS3 slabs and targeted recalculate_walls for AXIS2 walls. Each element's IfcMaterialLayerSetUsage attributes are now updated individually before regeneration. 2. slice_layerset_mesh (loader.py): Loader.unit_scale is a class variable only set during full file import, so it was stale (= 1) during live geometry updates on foot-based IFC files. Layer bisect planes were being computed in IFC feet while the mesh was in Blender metres, placing all cuts completely outside the mesh. Fixed by computing unit_scale fresh from the IFC file on each call via ifcopenshell.util.unit.calculate_unit_scale. 3. OffsetFromReferenceLine stale / layer order reversed (slab.py, loader.py): A guard (and custom_offset is None) in change_thickness prevented writing the correct OffsetFromReferenceLine (position.z) to the usage when a custom offset was active. This left the value at 0.0 instead of the actual slab bottom (e.g. -1.0 IFC units for a TOP-reference slab), so the bisect starting point co was at the reference plane rather than the slab bottom, reversing layer assignments or missing layers entirely. Removed the guard — safe because each element has its own IfcMaterialLayerSetUsage instance. Reverted the AXIS3 bisect normal back to (0,0,1) (upward from co at slab bottom) which is correct once OffsetFromReferenceLine is properly set.
This commit is contained in:
@@ -614,25 +614,29 @@ class EditAssignedMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
attributes=attributes,
|
||||
)
|
||||
|
||||
layer_sets_to_regenerate = set()
|
||||
slab_planer = slab.DumbSlabPlaner()
|
||||
wall_objs = []
|
||||
|
||||
for obj in objects:
|
||||
obj_element = tool.Ifc.get_entity(obj)
|
||||
obj_material_usage = ifcopenshell.util.element.get_material(obj_element)
|
||||
|
||||
if obj_material_usage and obj_material_usage.is_a("IfcMaterialLayerSetUsage"):
|
||||
obj_material_usage.OffsetFromReferenceLine = material.OffsetFromReferenceLine
|
||||
obj_material_usage.DirectionSense = material.DirectionSense
|
||||
obj_material_usage.ReferenceExtent = material.ReferenceExtent
|
||||
|
||||
layer_sets_to_regenerate.add(obj_material_usage.ForLayerSet)
|
||||
|
||||
# Save custom offset to BBIM_MaterialLayer pset
|
||||
tool.Model.save_custom_offset_to_pset(obj_element, obj)
|
||||
|
||||
for layer_set in layer_sets_to_regenerate:
|
||||
wall.DumbWallPlaner().regenerate_from_layer_set(layer_set)
|
||||
slab.DumbSlabPlaner().regenerate_from_layer_set(layer_set)
|
||||
# Targeted regeneration: only update this element's geometry, not
|
||||
# all elements sharing the layer set (which would corrupt unrelated instances).
|
||||
if obj_material_usage.LayerSetDirection == "AXIS3":
|
||||
slab_planer.regenerate_from_occurence(obj_element, obj_material_usage)
|
||||
elif obj_material_usage.LayerSetDirection == "AXIS2":
|
||||
wall_objs.append(obj)
|
||||
|
||||
if wall_objs:
|
||||
tool.Model.recalculate_walls(wall_objs)
|
||||
|
||||
if material_set_usage.is_a("IfcMaterialProfileSetUsage"):
|
||||
if "CardinalPoint" in attributes:
|
||||
|
||||
@@ -280,6 +280,8 @@ class DumbSlabPlaner:
|
||||
perpendicular_depth = thickness * abs(1 / cos(existing_x_angle))
|
||||
perpendicular_offset = layer_offset * abs(1 / cos(existing_x_angle)) / self.unit_scale
|
||||
|
||||
ifc_position = extrusion.Position
|
||||
|
||||
# Check angle and z direction to determine whether the extrusion direction is positive or negative
|
||||
if (abs(existing_x_angle) < (pi / 2) and direction_ratios.z > 0) or (
|
||||
abs(existing_x_angle) > (pi / 2) and direction_ratios.z < 0
|
||||
@@ -301,7 +303,6 @@ class DumbSlabPlaner:
|
||||
extrusion.ExtrudedDirection.DirectionRatios = tuple(direction_ratios)
|
||||
extrusion.Depth = perpendicular_depth
|
||||
|
||||
ifc_position = extrusion.Position
|
||||
position = offset_direction * perpendicular_offset
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material:
|
||||
|
||||
@@ -1063,12 +1063,16 @@ class Loader(bonsai.core.tool.Loader):
|
||||
|
||||
@classmethod
|
||||
def slice_layerset_mesh(cls, element: ifcopenshell.entity_instance, mesh: bpy.types.Mesh) -> bpy.types.Mesh:
|
||||
# Always compute unit_scale fresh — cls.unit_scale may be stale (e.g. during live
|
||||
# geometry updates that don't go through the full import pipeline).
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
if not (material := ifcopenshell.util.element.get_material(element)):
|
||||
return mesh
|
||||
elif material.is_a("IfcMaterialLayerSetUsage"):
|
||||
usage = material
|
||||
layer_set = material.ForLayerSet
|
||||
offset = usage.OffsetFromReferenceLine * cls.unit_scale
|
||||
offset = usage.OffsetFromReferenceLine * unit_scale
|
||||
sense_factor = 1 if usage.DirectionSense == "POSITIVE" else -1
|
||||
else:
|
||||
return mesh
|
||||
@@ -1101,7 +1105,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 * cls.unit_scale
|
||||
co += no * layer.LayerThickness * 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
|
||||
)
|
||||
@@ -1115,14 +1119,17 @@ class Loader(bonsai.core.tool.Loader):
|
||||
for face in bisect_geom["geom"]:
|
||||
if isinstance(face, bmesh.types.BMFace):
|
||||
center = face.calc_center_median()
|
||||
if (center - co).dot(no) >= 0:
|
||||
dot = (center - co).dot(no)
|
||||
if dot >= 0:
|
||||
face.material_index = material_index
|
||||
has_layer_styles = True
|
||||
else:
|
||||
for face in bisect_geom["geom"]:
|
||||
if isinstance(face, bmesh.types.BMFace):
|
||||
center = face.calc_center_median()
|
||||
if (center - co).dot(no) < 0 and (center - prev_co).dot(no) >= 0:
|
||||
dot_co = (center - co).dot(no)
|
||||
dot_prev = (center - prev_co).dot(no)
|
||||
if dot_co < 0 and dot_prev >= 0:
|
||||
face.material_index = material_index
|
||||
has_layer_styles = True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user