From 2feade01cb0282cc30cd03bde6504efb23965d42 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Wed, 27 May 2026 16:01:18 +0200 Subject: [PATCH] DRY tag-redraw-3D-viewports loops via tool.Blender.update_all_viewports Five inline copies of the same defensive pattern lived across ``tool/parametric.py``, ``bim/parametric_lifecycle.py``, ``bim/module/model/preview_base.py`` (twice), and as a near-twin in ``tool/blender.py:update_all_viewports`` itself. ``tool.Blender.update_all_viewports`` already covered the ``tag_redraw`` job but used an ``assert context.screen`` that would raise during background-mode operators or early-load_post calls where ``screen`` legitimately is None. Relax to a defensive ``getattr(context, "screen", None)`` + silent return so the helper fits every caller's needs, then collapse the 4 inline copies to single calls. Net -9 LOC. The helper now describes its contract ("silent no-op when no screen attached") rather than naming specific callers, so moving a caller doesn't rot the docstring. Generated with the assistance of an AI coding tool. --- src/bonsai/bonsai/bim/module/model/preview_base.py | 9 ++------- src/bonsai/bonsai/bim/parametric_lifecycle.py | 7 +------ src/bonsai/bonsai/tool/blender.py | 8 ++++++-- src/bonsai/bonsai/tool/parametric.py | 6 +----- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/preview_base.py b/src/bonsai/bonsai/bim/module/model/preview_base.py index f9921dd1aa..e99aadc2f4 100644 --- a/src/bonsai/bonsai/bim/module/model/preview_base.py +++ b/src/bonsai/bonsai/bim/module/model/preview_base.py @@ -126,9 +126,7 @@ def make_dim_setter( if props is None: return setattr(props, field, max(min_value, float(value))) - for area in bpy.context.screen.areas if bpy.context.screen else (): - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports() return _set @@ -179,10 +177,7 @@ def try_cancel_active_preview(context: bpy.types.Context) -> bool: getattr(bpy.ops.bim, op_name)() cancelled = True if cancelled: - screen = context.screen - for area in screen.areas if screen else (): - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports(context) return cancelled diff --git a/src/bonsai/bonsai/bim/parametric_lifecycle.py b/src/bonsai/bonsai/bim/parametric_lifecycle.py index 414f11173b..436a28396e 100644 --- a/src/bonsai/bonsai/bim/parametric_lifecycle.py +++ b/src/bonsai/bonsai/bim/parametric_lifecycle.py @@ -436,11 +436,7 @@ def resync_parametric_drafts_after_undo() -> None: if regenerator is None: continue regenerator(obj) - screen = getattr(bpy.context, "screen", None) - if screen is not None: - for area in screen.areas: - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports() @persistent @@ -464,4 +460,3 @@ def uninstall_parametric_lifecycle_handlers() -> None: hook.remove(_resync_on_undo) except ValueError: pass ->>>>>>> 8e305588d (fixup! Add parametric-draft undo-resync registry + handler hooks) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 0ecb767da1..c94ec38d72 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -680,9 +680,13 @@ class Blender(bonsai.core.tool.Blender): @classmethod def update_all_viewports(cls, context: bpy.types.Context | None = None) -> None: + """Tag every visible 3D viewport for redraw. Silent no-op when no + screen attached (background mode, plug-out, mid-load_post).""" context = context or bpy.context - assert context.screen - for area in context.screen.areas: + screen = getattr(context, "screen", None) + if screen is None: + return + for area in screen.areas: if area.type == "VIEW_3D": area.tag_redraw() diff --git a/src/bonsai/bonsai/tool/parametric.py b/src/bonsai/bonsai/tool/parametric.py index 0460379273..ad9846a18d 100644 --- a/src/bonsai/bonsai/tool/parametric.py +++ b/src/bonsai/bonsai/tool/parametric.py @@ -187,11 +187,7 @@ class Parametric(bonsai.core.tool.Parametric): cls._geom_generation += 1 bonsai.bim.handler.update_bim_tool_props() - screen = getattr(bpy.context, "screen", None) - if screen is not None: - for area in screen.areas: - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports() @classmethod def find_by_name(cls, name: str) -> Optional[ParametricObject]: