diff --git a/src/bonsai/bonsai/bim/module/drawing/gizmos.py b/src/bonsai/bonsai/bim/module/drawing/gizmos.py index c50828b324..071ede7ac3 100644 --- a/src/bonsai/bonsai/bim/module/drawing/gizmos.py +++ b/src/bonsai/bonsai/bim/module/drawing/gizmos.py @@ -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) diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index e7fa918d50..890e75e7b9 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -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 diff --git a/src/bonsai/bonsai/bim/module/model/preview_base.py b/src/bonsai/bonsai/bim/module/model/preview_base.py index e99aadc2f4..b58e6ea246 100644 --- a/src/bonsai/bonsai/bim/module/model/preview_base.py +++ b/src/bonsai/bonsai/bim/module/model/preview_base.py @@ -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 diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index 0f460a5f27..62898a9023 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -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: