mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 14:41:25 +00:00
Bonsai: preserve Z when editing FALL/slope annotation curves (#6557)
Editing a FALL (or SLOPE_ANGLE/FRACTION/PERCENT) annotation's endpoint in item edit mode and tabbing out reverted the Z move: export_curves() always flattened curve points through auto_detect_curves()'s .to_2d(), even though a fall/slope annotation's whole meaning is the Z difference between its two points. That is fine for genuinely 2D profile/annotation curves, but wrong for these slope indicators. Add a preserve_z flag to export_curves()/auto_detect_curves() and set it only when the edited curve belongs to an IfcAnnotation whose predefined type is FALL/SLOPE_ANGLE/SLOPE_FRACTION/SLOPE_PERCENT, so the exported IfcIndexedPolyCurve/IfcPolyline keeps 3D points (IfcCartesianPointList3D) instead of being flattened to 2D. All other curve-like items (profiles, dimensions, leaders, etc.) are unaffected since preserve_z defaults to False. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -2671,7 +2671,18 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
tool.Geometry.import_item_attributes(obj)
|
tool.Geometry.import_item_attributes(obj)
|
||||||
elif tool.Geometry.is_curvelike_item(item):
|
elif tool.Geometry.is_curvelike_item(item):
|
||||||
ProfileDecorator.uninstall()
|
ProfileDecorator.uninstall()
|
||||||
new = tool.Model.export_curves(obj)
|
# Fall / slope annotations encode their slope as a real Z difference
|
||||||
|
# between the curve's own points, so unlike ordinary 2D profile
|
||||||
|
# curves their Z must be preserved instead of being flattened.
|
||||||
|
preserve_z = False
|
||||||
|
if rep_obj and (rep_element := tool.Ifc.get_entity(rep_obj)) and rep_element.is_a("IfcAnnotation"):
|
||||||
|
preserve_z = ifcopenshell.util.element.get_predefined_type(rep_element) in (
|
||||||
|
"FALL",
|
||||||
|
"SLOPE_ANGLE",
|
||||||
|
"SLOPE_FRACTION",
|
||||||
|
"SLOPE_PERCENT",
|
||||||
|
)
|
||||||
|
new = tool.Model.export_curves(obj, preserve_z=preserve_z)
|
||||||
|
|
||||||
if not new:
|
if not new:
|
||||||
|
|
||||||
|
|||||||
@@ -289,13 +289,13 @@ class Model(bonsai.core.tool.Model):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def export_curves(
|
def export_curves(
|
||||||
cls, obj: bpy.types.Object, position: Optional[Matrix] = None
|
cls, obj: bpy.types.Object, position: Optional[Matrix] = None, preserve_z: bool = False
|
||||||
) -> list[ifcopenshell.entity_instance] | None:
|
) -> list[ifcopenshell.entity_instance] | None:
|
||||||
if position is None:
|
if position is None:
|
||||||
position = Matrix()
|
position = Matrix()
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
result = cls.auto_detect_curves(obj, obj.data, position)
|
result = cls.auto_detect_curves(obj, obj.data, position, preserve_z=preserve_z)
|
||||||
if isinstance(result, dict) and result["curves"]:
|
if isinstance(result, dict) and result["curves"]:
|
||||||
for curve in result["curves"]:
|
for curve in result["curves"]:
|
||||||
results.append(tool.Ifc.get().add(curve))
|
results.append(tool.Ifc.get().add(curve))
|
||||||
@@ -2494,8 +2494,17 @@ class Model(bonsai.core.tool.Model):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def auto_detect_curves(
|
def auto_detect_curves(
|
||||||
cls, obj: bpy.types.Object, mesh: bpy.types.Mesh, position: Matrix | None = None
|
cls,
|
||||||
|
obj: bpy.types.Object,
|
||||||
|
mesh: bpy.types.Mesh,
|
||||||
|
position: Matrix | None = None,
|
||||||
|
preserve_z: bool = False,
|
||||||
) -> Union[tuple, dict]:
|
) -> Union[tuple, dict]:
|
||||||
|
"""
|
||||||
|
:param preserve_z: Keep each point's Z coordinate instead of flattening
|
||||||
|
to a 2D curve. Used for annotations whose geometry has a meaningful
|
||||||
|
Z difference between points, such as a FALL/slope indicator.
|
||||||
|
"""
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
|
||||||
if position is None:
|
if position is None:
|
||||||
@@ -2651,18 +2660,20 @@ class Model(bonsai.core.tool.Model):
|
|||||||
segments[-1][0] = last_segment_indices
|
segments[-1][0] = last_segment_indices
|
||||||
curves.append(tmp.createIfcIndexedPolyCurve(points, segments))
|
curves.append(tmp.createIfcIndexedPolyCurve(points, segments))
|
||||||
elif tmp.schema == "IFC2X3":
|
elif tmp.schema == "IFC2X3":
|
||||||
points = [
|
coords = [(position_i @ v.co) / unit_scale for v in loop_verts]
|
||||||
tmp.createIfcCartesianPoint(list(((position_i @ v.co) / unit_scale).to_2d()))
|
points = [tmp.createIfcCartesianPoint(list(co if preserve_z else co.to_2d())) for co in coords]
|
||||||
for v in loop_verts
|
|
||||||
]
|
|
||||||
if is_closed:
|
if is_closed:
|
||||||
points.append(points[0])
|
points.append(points[0])
|
||||||
curves.append(tmp.createIfcPolyline(points))
|
curves.append(tmp.createIfcPolyline(points))
|
||||||
else: # Pure straight polyline, no segments required
|
else: # Pure straight polyline, no segments required
|
||||||
coord_list = [list(((position_i @ v.co) / unit_scale).to_2d()) for v in loop_verts]
|
coords = [(position_i @ v.co) / unit_scale for v in loop_verts]
|
||||||
|
coord_list = [list(co if preserve_z else co.to_2d()) for co in coords]
|
||||||
if is_closed:
|
if is_closed:
|
||||||
coord_list.append(coord_list[0])
|
coord_list.append(coord_list[0])
|
||||||
points = tmp.createIfcCartesianPointList2D(coord_list)
|
if preserve_z:
|
||||||
|
points = tmp.createIfcCartesianPointList3D(coord_list)
|
||||||
|
else:
|
||||||
|
points = tmp.createIfcCartesianPointList2D(coord_list)
|
||||||
curves.append(tmp.createIfcIndexedPolyCurve(points))
|
curves.append(tmp.createIfcIndexedPolyCurve(points))
|
||||||
|
|
||||||
return {"ifc_file": tmp, "curves": curves}
|
return {"ifc_file": tmp, "curves": curves}
|
||||||
|
|||||||
Reference in New Issue
Block a user