Hide sister gizmos during preview + ESC cancels + DRY wall polls

Three live-session regressions surfaced after the fillet feature
landed.

Sister gizmos competed with the active preview:
* preview_base.any_preview_active(context): new helper iterates the
  PREVIEW_CANCEL_OPS registry and returns True if any preview is open.
  Future previews registered there automatically gate sister gizmos.
* BaseParametricGizmoGroup.poll (gizmos.py): short-circuits on
  any_preview_active so every parametric gizmo (door/window/stair/
  roof/railing/wall edition) hides during ANY preview.
* The 4 wall gizmo groups with explicit polls (GizmoWallAddOpening,
  GizmoWallExtendVertically, GizmoWallJoinIntersection,
  GizmoWallUnjoinSingle) + GizmoWallFilletReedit gain the same gate.

DRY: extract _wall_gizmo_poll_gate(context):
* 5 wall gizmo polls each duplicated the 2 pre-flight checks
  (viewport-gizmos enabled + no preview active). The helper centralises
  them — each poll becomes a single short-circuit line followed by its
  per-feature selection inspection.

ESC cancels the active preview:
* try_cancel_active_preview already existed in preview_base since PR3
  but had no caller. Hooked into OverrideEscape.execute (geometry/
  operator.py) as a new elif branch — same keymap that already cancels
  pen gizmo edit mode + item mode + edit mode + aggregate mode. Order
  in the branch chain matters: try preview cancel before falling back
  to try_canceling_editing_modifier_parameters_or_path so the in-
  flight preview wins over a stale modifier-edit cancel attempt.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Gorgious56
2026-05-31 10:38:30 +02:00
parent 2114c1d5d0
commit 788d4fe8e8
4 changed files with 40 additions and 10 deletions
@@ -5283,6 +5283,14 @@ class BaseParametricGizmoGroup:
return False
if not tool.Blender.are_viewport_gizmos_enabled():
return False
# Hide every parametric gizmo while any preview is open — the preview
# is the only interactive surface in that mode, sister gizmos would
# compete for screen space and let the user trigger mutations that
# would race the preview's in-progress draft.
from bonsai.bim.module.model import preview_base
if preview_base.any_preview_active(context):
return False
if cls.gizmo_pref_name:
prefs = tool.Blender.get_addon_preferences()
feature_prefs = getattr(prefs.gizmos, cls.gizmo_pref_name, None)
@@ -60,6 +60,7 @@ import bonsai.core.root
import bonsai.core.spatial
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.model import preview_base
from bonsai.bim.module.model.decorator import ProfileDecorator
if TYPE_CHECKING:
@@ -2228,6 +2229,8 @@ class OverrideEscape(bpy.types.Operator):
bpy.ops.bim.hide_all_openings()
elif tool.Aggregate.get_aggregate_props().in_aggregate_mode:
bpy.ops.bim.disable_aggregate_mode()
elif preview_base.try_cancel_active_preview(context):
pass
elif active_object := context.active_object:
if tool.Blender.Modifier.try_canceling_editing_modifier_parameters_or_path(active_object):
pass
@@ -74,6 +74,17 @@ def is_preview_active(context: bpy.types.Context, attr: str) -> bool:
return bool(props is not None and props.is_active)
def any_preview_active(context: bpy.types.Context) -> bool:
"""``True`` if any registered preview is currently open. Sister gizmo
polls call this to hide themselves uniformly during ANY preview, so a
new preview registered in ``PREVIEW_CANCEL_OPS`` automatically gates
every parametric gizmo without each one growing a specific check."""
for attr, _op_name in PREVIEW_CANCEL_OPS:
if is_preview_active(context, attr):
return True
return False
# --- Lazy closure factories --------------------------------------------------
#
# Used by preview gizmo groups when wiring ``BIM_GT_gizmo_dimension``'s
+18 -10
View File
@@ -67,6 +67,19 @@ _FILLET_DEFAULT_LEG_FRACTION = 0.25 # Quarter of the shorter available leg —
_FILLET_MIN_RADIUS_M = 0.001 # Lower bound — anything smaller renders as a single pixel at common viewport scales.
def _wall_gizmo_poll_gate(context: bpy.types.Context) -> bool:
"""Common pre-flight gate every wall gizmo group's ``poll`` runs first:
viewport gizmos are enabled AND no preview is active. Centralises the
two checks every wall gizmo group otherwise duplicates inline; returning
``False`` here short-circuits the caller's poll before any per-feature
selection inspection runs."""
if not tool.Blender.are_viewport_gizmos_enabled():
return False
if preview_base.any_preview_active(context):
return False
return True
def regenerate_wall_mesh_from_props(obj: bpy.types.Object) -> None:
"""Rebuild ``obj.data`` as a preview box from ``BIMWallProperties`` without touching IFC.
@@ -3336,8 +3349,7 @@ class GizmoWallAddOpening(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMixin
@classmethod
def poll(cls, context: bpy.types.Context) -> bool:
prefs = tool.Blender.get_addon_preferences()
if not prefs.gizmos.draw_gizmos_in_3d_viewport:
if not _wall_gizmo_poll_gate(context):
return False
selected = tool.Blender.get_selected_objects()
if len(selected) != 2:
@@ -3405,8 +3417,7 @@ class GizmoWallExtendVertically(bpy.types.GizmoGroup, _WallGeomCachedBillboardin
@classmethod
def poll(cls, context: bpy.types.Context) -> bool:
prefs = tool.Blender.get_addon_preferences()
if not prefs.gizmos.draw_gizmos_in_3d_viewport:
if not _wall_gizmo_poll_gate(context):
return False
selected = tool.Blender.get_selected_objects()
if len(selected) != 2:
@@ -3493,8 +3504,7 @@ class GizmoWallJoinIntersection(bpy.types.GizmoGroup, _WallGeomCachedBillboardin
@classmethod
def poll(cls, context: bpy.types.Context) -> bool:
prefs = tool.Blender.get_addon_preferences()
if not prefs.gizmos.draw_gizmos_in_3d_viewport:
if not _wall_gizmo_poll_gate(context):
return False
selected = tool.Blender.get_selected_objects()
if len(selected) != 2:
@@ -3664,7 +3674,7 @@ class GizmoWallUnjoinSingle(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMix
@classmethod
def poll(cls, context: bpy.types.Context) -> bool:
if not tool.Blender.are_viewport_gizmos_enabled():
if not _wall_gizmo_poll_gate(context):
return False
active = tool.Blender.get_active_object(is_selected=True)
if active is None:
@@ -4032,9 +4042,7 @@ class GizmoWallFilletReedit(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMix
@classmethod
def poll(cls, context: bpy.types.Context) -> bool:
if not tool.Blender.are_viewport_gizmos_enabled():
return False
if _wall_fillet_preview_active(context):
if not _wall_gizmo_poll_gate(context):
return False
active = tool.Blender.get_active_object(is_selected=True)
if active is None: