Adopt _CommitWallDraftsFirstMixin on 7 wall operators

The 7 multi-wall operators (UnjoinWalls, UnjoinWallPathConnection,
ExtendWallsToUnderside, ExtendWallsToWall, SplitWall, MergeWall,
JoinWallsIntersection) each opened their _execute with an identical
prologue:

    _commit_pending_wall_edits_for_selection(context)
    # ... operator-specific logic

— flushing any in-progress wall parametric drafts so the operator
acts on committed IFC state rather than the draft preview box.

Extract that prologue into _CommitWallDraftsFirstMixin: its _execute
calls the commit helper, then delegates to a subclass-supplied
_perform. Subclasses inherit the mixin first in their bases tuple so
the mixin's _execute resolves first via the MRO. The IFC transaction
opened by tool.Ifc.Operator.execute still wraps both the commit and
the perform.

Behaviour-equivalent — same call, same order, same selection scope.
Architectural cleanup only: a future multi-wall operator can no
longer forget the commit step. The named helper
_commit_pending_wall_edits_for_selection stays as the single
encapsulation of the names=("wall",) filter; its docstring loses
the stale "every multi-wall operator calls it at the top of
_execute" sentence and now just describes the filter contract.

Matches gizmos-8088's _CommitWallDraftsFirstMixin pattern.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Gorgious56
2026-06-03 17:15:45 +02:00
committed by Thomas Krijnen
parent e0aa39068d
commit a3533bfa49
+39 -28
View File
@@ -191,7 +191,29 @@ def _resync_walls_after_mutation(objs: Iterable["bpy.types.Object | None"]) -> N
_maybe_resync_wall_props_from_ifc(obj) _maybe_resync_wall_props_from_ifc(obj)
class UnjoinWalls(bpy.types.Operator, tool.Ifc.Operator): class _CommitWallDraftsFirstMixin:
"""Operator mixin that flushes any in-progress wall parametric drafts in
the current selection before delegating to the subclass's ``_perform``.
Centralises the inline ``_commit_pending_wall_edits_for_selection(context)``
call that every multi-wall operator (unjoin, unjoin-path-connection,
extend-to-underside, extend-to-wall, split, merge, join-intersection)
used to repeat at the top of ``_execute``. Subclasses implement
``_perform`` instead of ``_execute``; the IFC transaction opened by
``tool.Ifc.Operator.execute`` wraps both the commit and the perform.
Place this BEFORE ``bpy.types.Operator`` in the bases tuple so the
mixin's ``_execute`` resolves first in the MRO."""
def _execute(self, context: bpy.types.Context):
_commit_pending_wall_edits_for_selection(context)
return self._perform(context)
def _perform(self, context: bpy.types.Context):
raise NotImplementedError("Subclasses of _CommitWallDraftsFirstMixin must implement _perform.")
class UnjoinWalls(_CommitWallDraftsFirstMixin, bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unjoin_walls" bl_idname = "bim.unjoin_walls"
bl_label = "Unjoin Walls" bl_label = "Unjoin Walls"
bl_description = "Unjoin the selected walls" bl_description = "Unjoin the selected walls"
@@ -204,13 +226,12 @@ class UnjoinWalls(bpy.types.Operator, tool.Ifc.Operator):
return False return False
return True return True
def _execute(self, context): def _perform(self, context):
_commit_pending_wall_edits_for_selection(context)
core.unjoin_walls(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model) core.unjoin_walls(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model)
_resync_walls_after_mutation(tool.Blender.get_selected_objects()) _resync_walls_after_mutation(tool.Blender.get_selected_objects())
class UnjoinWallPathConnection(bpy.types.Operator, tool.Ifc.Operator): class UnjoinWallPathConnection(_CommitWallDraftsFirstMixin, bpy.types.Operator, tool.Ifc.Operator):
"""Surgical counterpart to `UnjoinWalls`: disconnect the active wall from one """Surgical counterpart to `UnjoinWalls`: disconnect the active wall from one
specific partner wall, leaving the active wall's other connections intact. The specific partner wall, leaving the active wall's other connections intact. The
partner is identified by IFC GlobalId — invariant under Blender-object renames, partner is identified by IFC GlobalId — invariant under Blender-object renames,
@@ -231,8 +252,7 @@ class UnjoinWallPathConnection(bpy.types.Operator, tool.Ifc.Operator):
return False return False
return True return True
def _execute(self, context): def _perform(self, context):
_commit_pending_wall_edits_for_selection(context)
active = tool.Blender.get_active_object(is_selected=True) active = tool.Blender.get_active_object(is_selected=True)
if not active: if not active:
self.report({"ERROR"}, "Could not resolve walls for surgical unjoin.") self.report({"ERROR"}, "Could not resolve walls for surgical unjoin.")
@@ -284,7 +304,7 @@ class UnjoinWallPathConnection(bpy.types.Operator, tool.Ifc.Operator):
_resync_walls_after_mutation([active, other]) _resync_walls_after_mutation([active, other])
class ExtendWallsToUnderside(bpy.types.Operator, tool.Ifc.Operator): class ExtendWallsToUnderside(_CommitWallDraftsFirstMixin, bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.extend_walls_to_underside" bl_idname = "bim.extend_walls_to_underside"
bl_label = "Extend Walls To Underside" bl_label = "Extend Walls To Underside"
bl_description = "Extend and clip selected walls at the bottom faces of an object" bl_description = "Extend and clip selected walls at the bottom faces of an object"
@@ -297,11 +317,7 @@ class ExtendWallsToUnderside(bpy.types.Operator, tool.Ifc.Operator):
return False return False
return True return True
def _execute(self, context): def _perform(self, context):
# Match the sibling ops (UnjoinWalls / MergeWall / ExtendWallsToWall): if any
# of the selected walls has an in-progress parametric draft, commit it before
# extending, so the slab clip operates on the just-finalised IFC state.
_commit_pending_wall_edits_for_selection(context)
slabs: list[bpy.types.Object] = [] slabs: list[bpy.types.Object] = []
walls: list[bpy.types.Object] = [] walls: list[bpy.types.Object] = []
for obj in tool.Blender.get_selected_objects(): for obj in tool.Blender.get_selected_objects():
@@ -337,14 +353,13 @@ class RegenerateWallToUnderside(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "Please select at least one LAYER2 element") self.report({"ERROR"}, "Please select at least one LAYER2 element")
class ExtendWallsToWall(bpy.types.Operator, tool.Ifc.Operator): class ExtendWallsToWall(_CommitWallDraftsFirstMixin, bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.extend_walls_to_wall" bl_idname = "bim.extend_walls_to_wall"
bl_label = "Extend Walls To Wall" bl_label = "Extend Walls To Wall"
bl_description = "Extend and trim selected walls to another wall" bl_description = "Extend and trim selected walls to another wall"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _perform(self, context):
_commit_pending_wall_edits_for_selection(context)
target_obj = None target_obj = None
objs = [] objs = []
if ( if (
@@ -557,7 +572,7 @@ class FlipWall(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"} return {"FINISHED"}
class SplitWall(bpy.types.Operator, tool.Ifc.Operator): class SplitWall(_CommitWallDraftsFirstMixin, bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.split_wall" bl_idname = "bim.split_wall"
bl_label = "Split Wall" bl_label = "Split Wall"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -572,8 +587,7 @@ class SplitWall(bpy.types.Operator, tool.Ifc.Operator):
return False return False
return True return True
def _execute(self, context): def _perform(self, context):
_commit_pending_wall_edits_for_selection(context)
selected_objs = tool.Model.get_selected_mesh_objects() selected_objs = tool.Model.get_selected_mesh_objects()
for obj in selected_objs: for obj in selected_objs:
DumbWallJoiner().split(obj, context.scene.cursor.location) DumbWallJoiner().split(obj, context.scene.cursor.location)
@@ -581,7 +595,7 @@ class SplitWall(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"} return {"FINISHED"}
class MergeWall(bpy.types.Operator, tool.Ifc.Operator): class MergeWall(_CommitWallDraftsFirstMixin, bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.merge_wall" bl_idname = "bim.merge_wall"
bl_label = "Merge Wall" bl_label = "Merge Wall"
bl_description = "Merge selected walls into one object" bl_description = "Merge selected walls into one object"
@@ -601,8 +615,7 @@ class MergeWall(bpy.types.Operator, tool.Ifc.Operator):
return False return False
return True return True
def _execute(self, context): def _perform(self, context):
_commit_pending_wall_edits_for_selection(context)
active_obj = context.active_object active_obj = context.active_object
assert active_obj assert active_obj
selected_objs = tool.Model.get_selected_mesh_objects() selected_objs = tool.Model.get_selected_mesh_objects()
@@ -2241,11 +2254,10 @@ def _commit_active_wall_edit_if_any(context: bpy.types.Context) -> bpy.types.Obj
def _commit_pending_wall_edits_for_selection(context: bpy.types.Context) -> None: # noqa: ARG001 def _commit_pending_wall_edits_for_selection(context: bpy.types.Context) -> None: # noqa: ARG001
"""Thin wall-scoped alias for `tool.Parametric.commit_pending_edits_for_selection`. """Thin wall-scoped alias for ``tool.Parametric.commit_pending_edits_for_selection``.
Kept as a named helper because every multi-wall operator (split / join / merge / Encapsulates the ``names=("wall",)`` filter so the registry name is
unjoin / extend-to-wall …) calls it at the top of ``_execute``; centralising the touched in exactly one place."""
``names=("wall",)`` filter here means the registry name is touched in one place."""
tool.Parametric.commit_pending_edits_for_selection(names=("wall",)) tool.Parametric.commit_pending_edits_for_selection(names=("wall",))
@@ -4053,7 +4065,7 @@ class GizmoWallFilletToggleOpenings(bpy.types.GizmoGroup, _WallGeomCachedBillboa
self.toggle_openings_icon.hide = False self.toggle_openings_icon.hide = False
class JoinWallsIntersection(bpy.types.Operator, tool.Ifc.Operator): class JoinWallsIntersection(_CommitWallDraftsFirstMixin, bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.join_walls_intersection" bl_idname = "bim.join_walls_intersection"
bl_label = "Join Walls at Corner" bl_label = "Join Walls at Corner"
bl_description = "Join two walls at their corner" bl_description = "Join two walls at their corner"
@@ -4066,8 +4078,7 @@ class JoinWallsIntersection(bpy.types.Operator, tool.Ifc.Operator):
return False return False
return True return True
def _execute(self, context: bpy.types.Context) -> set[str]: def _perform(self, context: bpy.types.Context) -> set[str]:
_commit_pending_wall_edits_for_selection(context)
try: try:
core.join_walls_LV(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model) core.join_walls_LV(tool.Ifc, tool.Blender, tool.Geometry, DumbWallJoiner(), tool.Model)
except core.RequireTwoWallsError as e: except core.RequireTwoWallsError as e: