From 8faf9ff43d280653e4855a023994f823da9eb08f Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Fri, 5 Jun 2026 13:20:49 +0200 Subject: [PATCH] Consolidate load_post parametric drains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bim/handler.py was importing two feature-module internals (wall_offset_gizmos.clear_caches, preview_base.discard_pending_previews) to drain load-transient parametric state alongside the existing tool.Parametric.heal_stale_edit_flags() call inside _apply_save_file_invariants. Each new parametric drain added one top-level import and one inline call — every load_post drain leaked into handler.py's namespace. Hide all three drains behind tool.Parametric.on_load_post(scene), sited adjacent to heal_stale_edit_flags. The two feature-module imports become late imports inside on_load_post — same pattern as refresh_post_commit's existing `import bonsai.bim.handler` — which sidesteps the tool.parametric -> bim.module.model.preview_base -> bonsai.tool registration-time cycle. The forward-compat AST contract that pinned "every module-scope GenerationKeyedCache + clear_caches MUST be drained on load_post" follows the call site to its new home — the test now walks tool.Parametric.on_load_post instead of _apply_save_file_invariants. No behaviour change. 45/45 affected bim tests pass (test_handler_forward_compat, test_preview_base, test_wall_offset_gizmos, test_parametric_registry). ruff + black clean on all touched files. Generated with the assistance of an AI coding tool. --- src/bonsai/bonsai/bim/handler.py | 10 +++------- src/bonsai/bonsai/tool/parametric.py | 12 ++++++++++++ src/bonsai/test/bim/test_handler_forward_compat.py | 9 +++++---- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py index 8a62dfbdf4..58647d55bc 100644 --- a/src/bonsai/bonsai/bim/handler.py +++ b/src/bonsai/bonsai/bim/handler.py @@ -41,7 +41,6 @@ from bonsai.bim.decorator_cache import ( from bonsai.bim.ifc import IfcStore, get_cache_or_detect_lock from bonsai.bim.module.aggregate.decorator import AggregateDecorator from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator -from bonsai.bim.module.model import wall_offset_gizmos from bonsai.bim.module.model.array import ( ArrayPreviewDecorator, ArraySelectionHighlightDecorator, @@ -53,7 +52,6 @@ from bonsai.bim.module.model.decorator import ( WallAxisDecorator, WallFilletPreviewDecorator, ) -from bonsai.bim.module.model.preview_base import discard_pending_previews from bonsai.bim.module.model.wall import WallGizmoPreviewDecorator from bonsai.bim.module.nest.decorator import NestDecorator @@ -436,8 +434,8 @@ def subscribe_to_viewport_shading_changes(): def _apply_save_file_invariants(scene: bpy.types.Scene) -> None: """Invariants enforced on every load_post: msgbus subscription, IFC owner - settings, scene-bound caches, draft-flag healing, multi-instance lock probe, - and previews discarded so saved preview state never resurfaces on reopen.""" + settings, scene-bound caches, load-transient parametric state, and the + multi-instance lock probe.""" global global_subscription_owner active_object_key = bpy.types.LayerObjects, "active" bpy.msgbus.subscribe_rna( @@ -448,9 +446,7 @@ def _apply_save_file_invariants(scene: bpy.types.Scene) -> None: ifcopenshell.api.owner.settings.get_application = get_application AuthoringData.type_thumbnails = {} - tool.Parametric.heal_stale_edit_flags() - discard_pending_previews(scene) - wall_offset_gizmos.clear_caches() + tool.Parametric.on_load_post(scene) if tool.Ifc.get() and bpy.data.is_saved: props = tool.Blender.get_bim_props() diff --git a/src/bonsai/bonsai/tool/parametric.py b/src/bonsai/bonsai/tool/parametric.py index 840d21dc42..9cc8aff24f 100644 --- a/src/bonsai/bonsai/tool/parametric.py +++ b/src/bonsai/bonsai/tool/parametric.py @@ -271,6 +271,18 @@ class Parametric(bonsai.core.tool.Parametric): for obj in bpy.data.objects: cls._validated_editing_feature(obj) + @classmethod + def on_load_post(cls, scene: bpy.types.Scene) -> None: + """Drain load-transient parametric state on a freshly opened scene + so no draft edit flag, preview flag, or cache entry persists from + the saved file.""" + from bonsai.bim.module.model import wall_offset_gizmos + from bonsai.bim.module.model.preview_base import discard_pending_previews + + cls.heal_stale_edit_flags() + discard_pending_previews(scene) + wall_offset_gizmos.clear_caches() + @classmethod def get_pending_edits(cls) -> list[tuple[bpy.types.Object, str]]: """``(object, finish_operator_bl_idname)`` pairs for every object diff --git a/src/bonsai/test/bim/test_handler_forward_compat.py b/src/bonsai/test/bim/test_handler_forward_compat.py index 150a67bc8d..a840c3ba6b 100644 --- a/src/bonsai/test/bim/test_handler_forward_compat.py +++ b/src/bonsai/test/bim/test_handler_forward_compat.py @@ -146,7 +146,7 @@ def _modules_with_module_scope_cache_and_clear(): yield path.stem -def test_apply_save_file_invariants_drains_every_module_scope_geom_cache(handler_tree: ast.Module) -> None: +def test_on_load_post_drains_every_module_scope_geom_cache() -> None: """Module-scope ``GenerationKeyedCache`` instances persist across file loads — the counter they invalidate against is class-level and survives a ``.blend`` reload. Without a ``load_post`` drain the cache may serve @@ -154,9 +154,10 @@ def test_apply_save_file_invariants_drains_every_module_scope_geom_cache(handler freed ``bpy.data``, raising ``ReferenceError`` on the next attribute read. Pin: every model module that exposes both a module-scope cache and a - top-level ``clear_caches`` is called from ``_apply_save_file_invariants``, + top-level ``clear_caches`` is called from ``tool.Parametric.on_load_post``, the central post-load drain.""" - fn = _function_node(handler_tree, "_apply_save_file_invariants") + parametric_tree = ast.parse(PARAMETRIC_PATH.read_text(encoding="utf-8")) + fn = _function_node(parametric_tree, "on_load_post") drained: set[str] = set() for node in ast.walk(fn): if ( @@ -170,7 +171,7 @@ def test_apply_save_file_invariants_drains_every_module_scope_geom_cache(handler if missing: pytest.fail( "Module(s) expose a module-scope GenerationKeyedCache + clear_caches() but " - f"_apply_save_file_invariants does not drain them on load_post: {sorted(missing)}. " + f"tool.Parametric.on_load_post does not drain them on load_post: {sorted(missing)}. " "Add a `.clear_caches()` call so freshly-loaded files cannot serve " "entries holding freed bpy.data references from the previous file." )