Fix IfcGridAxis duplication losing geometry on save

When duplicating an IfcGridAxis one or more times before saving,
duplicates shared the same IfcPolyline as the source via shallow copy.
This caused two issues: (1) updating any one axis's AxisCurve during
export would destroy the shared curve, corrupting others; (2) duplicates
whose matrix_world checksum happened to match their current position were
skipped entirely by the is_moved guard, so their moved position was never
written to IFC.

Three fixes:

- geometry.py: call create_axis_curve immediately after copy_class for
  IfcGridAxis duplicates, so each new axis owns its AxisCurve from the
  moment of duplication rather than sharing the source's.

- create_axis_curve.py: only remove the old AxisCurve when its inverse
  count drops to zero, preventing destruction of curves still referenced
  by other axes.

- export_ifc.py: move the IfcGridAxis branch before the is_moved guard.
  Grid axes store position in AxisCurve geometry rather than
  ObjectPlacement, so is_moved is not a reliable gate. The internal
  matrices_differ check is the correct decision point, and
  record_object_position at the end keeps checksums in sync.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Ryan Schultz
2026-03-22 21:20:15 -05:00
parent e609f10559
commit c0889c7f10
3 changed files with 8 additions and 3 deletions
+2 -2
View File
@@ -120,10 +120,10 @@ class IfcExporter:
# updata_representation will run edit_object_placement if object is scaled
# and had no openings.
return element
if not tool.Ifc.is_moved(obj):
return
if element.is_a("IfcGridAxis"):
return self.sync_grid_axis_object_placement(obj, element)
if not tool.Ifc.is_moved(obj):
return
if not hasattr(element, "ObjectPlacement"):
return
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
+5
View File
@@ -2670,6 +2670,11 @@ class Geometry(bonsai.core.tool.Geometry):
# copy the actual class
new = bonsai.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
# Give each duplicated IfcGridAxis its own AxisCurve so it doesn't
# share geometry with the source axis.
if new and new.is_a("IfcGridAxis"):
tool.Model.create_axis_curve(new_obj, new)
# clean up the orphaned mesh with ifc id of the original object to avoid confusion
# IfcGridAxis keeps the same mesh data (it's pointing to ifc id 0, so it's not a problem)
if new and temp_data and not new.is_a("IfcGridAxis"):
@@ -89,5 +89,5 @@ def create_axis_curve(
),
)
if existing_curve:
if existing_curve and file.get_total_inverses(existing_curve) == 0:
ifcopenshell.util.element.remove_deep2(file, existing_curve)