Refactor bim/parametric_lifecycle — drift triad + Cancel polish

Three changes to the shared Enable/Finish/Cancel mixins:

1. Always-on drift triad on ParametricEditMixinBase. The base now
   provides ``_handle_drift_on_enable`` / ``_handle_drift_on_finish``
   / ``_handle_drift_on_cancel`` classmethods, called from the
   per-mixin ``_enable_one`` / ``_finish_one`` / ``_cancel_one``.
   Pre-edit Blender-side translations commit to IFC on Enable
   (apply_scale=False — only translation/rotation, not the user's
   accidental scale), in-edit drag commits on Finish (apply_scale=True),
   and Cancel restores the committed IFC placement via
   ``restore_or_rebaseline_placement``. Prevents the
   "uncommitted drag disappears on Finish" and "preview snaps back
   on Cancel" UX bugs.

2. ``_ParametricEditMixinBase`` renamed to ``ParametricEditMixinBase``
   (public). Per-feature mixins that need to subclass directly
   (e.g., when neither FeatureModifier nor PathPreserving fits)
   can do so without reaching into a private name.

3. ``_update_modifier_bmesh`` (PathPreserving) renamed to
   ``_restore_viewport_after_cancel``. The old name was inaccurate
   for subclasses that load a different IFC representation on
   Cancel rather than rebuilding a bmesh preview from props.

Plus two polish changes:

* ``_mark_type_thumbnail_dirty`` helper on the base centralises the
  ``ifcopenshell.util.element.get_type`` + thumbnail-mark pattern
  that both mixins repeated inline.
* ``FeatureModifierEditMixin._cancel_one`` and
  ``PathPreservingEditMixin._cancel_one`` wrap the restore in
  ``try/finally`` so ``props.is_editing = False`` flips even on
  partial restore failure. Without this, a Cancel that raised
  mid-restore would leave the user locked out of the edit lifecycle.
* ``PathPreservingEditMixin._finish_one`` / ``_cancel_one`` skip the
  pset commit + viewport rebuild when the draft equals the stored
  pset (no-op Enable→Finish round-trip should not pollute the
  representation list or burn an undo entry).

``FeatureModifierEditMixin._finish_one`` now routes the pset commit
through ``tool.Pset.write_bbim_data`` instead of inlining the
``createIfcText(json.dumps(...))`` + ``ifcopenshell.api.pset.edit_pset``
dance. Two test assertions updated to match.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Gorgious56
2026-05-27 15:50:42 +02:00
committed by Thomas Krijnen
parent ed6530f68b
commit aba9986628
2 changed files with 149 additions and 67 deletions
@@ -198,10 +198,12 @@ def test_feature_modifier_finish_one_clears_is_editing_and_writes_pset(patched_t
assert props.is_editing is False
assert obj in cls.representations_called
# edit_pset is called exactly once; properties key is "Data" wrapping JSON.
patched_tool_and_ifc["ifc"].api.pset.edit_pset.assert_called_once()
kwargs = patched_tool_and_ifc["ifc"].api.pset.edit_pset.call_args.kwargs
assert "properties" in kwargs and "Data" in kwargs["properties"]
# tool.Pset.write_bbim_data is called exactly once with the merged dict.
patched_tool_and_ifc["tool"].Pset.write_bbim_data.assert_called_once()
call_args = patched_tool_and_ifc["tool"].Pset.write_bbim_data.call_args
assert call_args.args[1] == "BBIM_Door" # pset_name positional arg
written_data = call_args.args[2]
assert "lining_properties" in written_data and "panel_properties" in written_data
def test_feature_modifier_finish_one_exception_leaves_draft_in_progress(patched_tool_and_ifc):
@@ -302,7 +304,7 @@ def _path_mixin_cls(match=True):
cls.ifc_data_updates.append(obj)
@classmethod
def _update_modifier_bmesh(cls, obj, context):
def _restore_viewport_after_cancel(cls, obj, context):
cls.bmesh_updates.append(obj)
return _TestPathMixin
@@ -344,7 +346,7 @@ def test_path_preserving_finish_one_preserves_path_data_and_clears_is_editing(pa
assert obj in cls.ifc_data_updates
def test_path_preserving_cancel_one_calls_update_modifier_bmesh(patched_tool_and_ifc):
def test_path_preserving_cancel_one_calls_restore_viewport_after_cancel(patched_tool_and_ifc):
props = _FakePathProps()
props.is_editing = True
obj = _make_obj(props)