mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 06:48:30 +00:00
Fix horizontal profile editing for sloped LAYER3 slabs
Editing a sloped slab's profile via direct_profile_edit / enable_editing_extrusion_profile presented the profile tilted in the slab plane instead of as a horizontal plan-view footprint. LAYER3 slabs carry their slope in the object placement (rotation_euler.x), so the object must be flattened to plan view and the profile projected onto the horizontal plane while editing, then un-projected on export. The object-flatten and slope-restore were gated behind `if extrusion.Position:`, but slabs commonly have `extrusion.Position is None`, so the object was never flattened and the profile stayed tilted. Move the flatten/restore out of that guard so it runs unconditionally for LAYER3. Project by scaling raw vertices (curve-type agnostic), stash the slope on the profile mesh so it is auto-discarded by switch_representation on save/cancel, and re-flatten on the invalid-profile path. Wall / non-LAYER3 behavior is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -636,8 +636,15 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
||||||
layer_params = tool.Model.get_material_layer_parameters(element)
|
layer_params = tool.Model.get_material_layer_parameters(element)
|
||||||
|
|
||||||
# TODO: review #7537 properly, this is a quick fix but something doesn't seem right.
|
# LAYER3 slabs carry their slope in the object placement (rotation_euler.x), not in
|
||||||
original_rotation_x = 0
|
# the extrusion direction, so get_existing_x_angle() reports ~0 for them. To let the
|
||||||
|
# user edit the true horizontal footprint we flatten the object to a plan view and
|
||||||
|
# project the profile onto the horizontal plane (restores the intent of #7537).
|
||||||
|
usage_type = tool.Model.get_usage_type(element)
|
||||||
|
is_layer3 = usage_type == "LAYER3"
|
||||||
|
# Capture the placement slope before we flatten it; reused on export.
|
||||||
|
layer3_x_angle = obj.rotation_euler.x if is_layer3 else 0.0
|
||||||
|
|
||||||
if extrusion.Position:
|
if extrusion.Position:
|
||||||
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
||||||
position.translation *= self.unit_scale
|
position.translation *= self.unit_scale
|
||||||
@@ -650,17 +657,35 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
tranlation_matrix = Matrix.Translation(rot_offset)
|
tranlation_matrix = Matrix.Translation(rot_offset)
|
||||||
position = position @ tranlation_matrix
|
position = position @ tranlation_matrix
|
||||||
|
|
||||||
# Restore Object rotation to zero
|
if not is_layer3:
|
||||||
local_rot_mat = obj.rotation_euler.to_matrix()
|
# Wall-style slope lives in the extrusion direction; undo it on the object.
|
||||||
rot_mat = Matrix.Rotation(-existing_x_angle, 4, "X")
|
local_rot_mat = obj.rotation_euler.to_matrix()
|
||||||
new_rot_mat = local_rot_mat.to_4x4() @ rot_mat
|
rot_mat = Matrix.Rotation(-existing_x_angle, 4, "X")
|
||||||
new_rot_euler = new_rot_mat.to_euler()
|
new_rot_mat = local_rot_mat.to_4x4() @ rot_mat
|
||||||
obj.rotation_euler = new_rot_euler
|
new_rot_euler = new_rot_mat.to_euler()
|
||||||
|
obj.rotation_euler = new_rot_euler
|
||||||
|
|
||||||
else:
|
else:
|
||||||
position = Matrix()
|
position = Matrix()
|
||||||
|
|
||||||
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position, x_angle=existing_x_angle)
|
if is_layer3:
|
||||||
|
# Flatten to plan view: drop the X slope, keep any Z rotation. This must run even
|
||||||
|
# when extrusion.Position is None (common for slabs) — the previous code gated it
|
||||||
|
# behind `if extrusion.Position` and so left sloped slabs tilted during editing.
|
||||||
|
obj.rotation_euler.x = 0.0
|
||||||
|
|
||||||
|
# Import unscaled, then project Y onto the horizontal plane so the profile reads
|
||||||
|
# as a true footprint. Done on raw vertices to cover every curve type uniformly.
|
||||||
|
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position, x_angle=0)
|
||||||
|
scale_factor = abs(cos(layer3_x_angle))
|
||||||
|
if scale_factor > 1e-6:
|
||||||
|
for vert in obj.data.vertices:
|
||||||
|
vert.co.y *= scale_factor
|
||||||
|
# Stash the slope on the profile mesh so exit can un-project it. It is discarded
|
||||||
|
# automatically when switch_representation swaps the mesh back on save or cancel.
|
||||||
|
obj.data["bim_profile_edit_x_angle"] = layer3_x_angle
|
||||||
|
else:
|
||||||
|
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position, x_angle=existing_x_angle)
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_extrusion_profile(context))
|
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_extrusion_profile(context))
|
||||||
@@ -688,6 +713,13 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
||||||
layer_params = tool.Model.get_material_layer_parameters(element)
|
layer_params = tool.Model.get_material_layer_parameters(element)
|
||||||
|
|
||||||
|
# Mirror of EnableEditingExtrusionProfile: LAYER3 slabs were flattened to a plan view
|
||||||
|
# for editing, so recover the stashed slope to un-project them on export.
|
||||||
|
usage_type = tool.Model.get_usage_type(element)
|
||||||
|
is_layer3 = usage_type == "LAYER3"
|
||||||
|
layer3_x_angle = obj.data.get("bim_profile_edit_x_angle", 0.0) if is_layer3 else 0.0
|
||||||
|
layer3_scale_factor = abs(cos(layer3_x_angle)) if is_layer3 else 1.0
|
||||||
|
|
||||||
if extrusion.Position:
|
if extrusion.Position:
|
||||||
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
||||||
position.translation *= self.unit_scale
|
position.translation *= self.unit_scale
|
||||||
@@ -700,20 +732,37 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
tranlation_matrix = Matrix.Translation(rot_offset)
|
tranlation_matrix = Matrix.Translation(rot_offset)
|
||||||
position = position @ tranlation_matrix
|
position = position @ tranlation_matrix
|
||||||
|
|
||||||
# Restore Object rotation to x_angle
|
if not is_layer3:
|
||||||
local_rot_mat = obj.rotation_euler.to_matrix()
|
# Restore Object rotation to x_angle
|
||||||
rot_mat = Matrix.Rotation(existing_x_angle, 4, "X")
|
local_rot_mat = obj.rotation_euler.to_matrix()
|
||||||
new_rot_mat = local_rot_mat.to_4x4() @ rot_mat
|
rot_mat = Matrix.Rotation(existing_x_angle, 4, "X")
|
||||||
new_rot_euler = new_rot_mat.to_euler()
|
new_rot_mat = local_rot_mat.to_4x4() @ rot_mat
|
||||||
obj.rotation_euler = new_rot_euler
|
new_rot_euler = new_rot_mat.to_euler()
|
||||||
|
obj.rotation_euler = new_rot_euler
|
||||||
|
|
||||||
else:
|
else:
|
||||||
position = Matrix()
|
position = Matrix()
|
||||||
|
|
||||||
profile = tool.Model.export_profile(obj, position=position, x_angle=existing_x_angle)
|
if is_layer3:
|
||||||
|
# Restore the plan-view slope we removed on enter (runs regardless of Position).
|
||||||
|
obj.rotation_euler.x = layer3_x_angle
|
||||||
|
# Un-project the horizontal footprint back into the slab plane before export.
|
||||||
|
if layer3_scale_factor > 1e-6:
|
||||||
|
for vert in obj.data.vertices:
|
||||||
|
vert.co.y /= layer3_scale_factor
|
||||||
|
profile = tool.Model.export_profile(obj, position=position, x_angle=0)
|
||||||
|
else:
|
||||||
|
profile = tool.Model.export_profile(obj, position=position, x_angle=existing_x_angle)
|
||||||
|
|
||||||
if not profile:
|
if not profile:
|
||||||
|
|
||||||
|
if is_layer3:
|
||||||
|
# Re-flatten so the user can keep fixing the profile in plan view.
|
||||||
|
if layer3_scale_factor > 1e-6:
|
||||||
|
for vert in obj.data.vertices:
|
||||||
|
vert.co.y *= layer3_scale_factor
|
||||||
|
obj.rotation_euler.x = 0.0
|
||||||
|
|
||||||
def msg(self, context):
|
def msg(self, context):
|
||||||
self.layout.label(text="INVALID PROFILE")
|
self.layout.label(text="INVALID PROFILE")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user