Bonsai: support editing IfcGeometricCurveSet representation items (#6407)

Entering item edit mode on an IfcGeometricCurveSet (the representation item
produced for annotations such as SECTION_LEVEL, whose Elements are curves)
reported "Editing IfcGeometricCurveSet geometry is not supported":
tool.Geometry.is_curvelike_item did not recognise the set, so the edit path
fell through to the unsupported branch.

Handle the set as a single editable item whose nested curves are its
Elements, mirroring how the join operator already treats it:
- is_curvelike_item recognises IfcGeometricCurveSet.
- tool.Model.convert_curve_to_mesh recurses into curve.Elements, importing
  each nested curve as its own edge loop in the mesh.
- On save (both OverrideModeSetObject and DirectProfileEdit exit paths),
  keep the same set entity and swap out its Elements (remove_deep2 on the
  old ones), instead of replacing the set with a bare curve.

Verified live in headless Blender: is_curvelike_item(IfcGeometricCurveSet)
is now True and the set imports to a mesh (was the exact "not supported"
error); a no-op edit round-trips for both a single-element and a two-element
set, preserving the set entity with new elements at identical coordinates
and removing the old ones; a plain (non-set) IfcIndexedPolyCurve item still
goes through the original replace-and-remove path (no regression).

Scope note: a multi-element set on a DIMENSION-style annotation is still
limited by the pre-existing ensure_annotation_vertex_order helper, which
walks one connected edge path and drops other loops. That is a separate,
pre-existing helper bug (not introduced here) and is left for a follow-up;
single-element and non-annotation curve sets round-trip fully.

Generated with the assistance of an AI coding tool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Petru Conduraru
2026-07-12 18:35:43 +03:00
parent 980988f208
commit e5f7f3211a
3 changed files with 36 additions and 1 deletions
@@ -2683,6 +2683,19 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
self.enable_edit_mode(bpy.context)
return
if item.is_a("IfcGeometricCurveSet"):
# The set itself is the edited representation item (e.g. a dimension
# annotation). Keep the same set entity and just swap out its nested
# curve Elements, rather than replacing the set with a bare curve.
old_elements = list(item.Elements)
item.Elements = tuple(new)
for old_element in old_elements:
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_element)
tool.Ifc.link(item, obj.data)
tool.Geometry.import_item(obj)
tool.Geometry.reload_representation(props.representation_obj)
return
additional_curves = []
if len(new) > 1:
additional_curves = new[1:]
@@ -2886,12 +2899,27 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
ProfileDecorator.install(context)
return {"CANCELLED"}
ifc_file = tool.Ifc.get()
if item.is_a("IfcGeometricCurveSet"):
# The set itself is the edited representation item (e.g. a dimension
# annotation). Keep the same set entity and just swap out its nested
# curve Elements, rather than replacing the set with a bare curve.
old_elements = list(item.Elements)
item.Elements = tuple(new)
for old_element in old_elements:
ifcopenshell.util.element.remove_deep2(ifc_file, old_element)
tool.Ifc.link(item, obj.data)
tool.Geometry.import_item(obj)
tool.Geometry.reload_representation(props.representation_obj)
tool.Geometry.disable_item_mode()
return {"FINISHED"}
additional_curves = []
if len(new) > 1:
additional_curves = new[1:]
new = new[0]
ifc_file = tool.Ifc.get()
for inverse in ifc_file.get_inverse(item):
ifcopenshell.util.element.replace_attribute(inverse, item, new)
ifcopenshell.util.element.remove_deep2(ifc_file, item)
+1
View File
@@ -1348,6 +1348,7 @@ class Geometry(bonsai.core.tool.Geometry):
or item.is_a("IfcCompositeCurve")
or item.is_a("IfcIndexedPolyCurve")
or item.is_a("IfcCircle")
or item.is_a("IfcGeometricCurveSet")
)
@classmethod
+6
View File
@@ -625,6 +625,12 @@ class Model(bonsai.core.tool.Model):
for segment in curve.Segments:
cls.convert_curve_to_mesh(obj, position, segment.ParentCurve)
elif curve.is_a("IfcGeometricCurveSet"):
# A set of independent curves (e.g. dimension annotations). Each element is
# imported as its own disconnected edge loop within the same mesh.
for sub_curve in curve.Elements:
cls.convert_curve_to_mesh(obj, position, sub_curve, x_angle=x_angle)
elif curve.is_a("IfcIndexedPolyCurve"):
for local_point in curve.Points.CoordList:
global_point = position @ Vector(cls.convert_unit_to_si(local_point)).to_3d()