From 3f5273744df6892285f2b0256a2b9590f729d25d Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Sun, 31 May 2026 19:10:48 +0200 Subject: [PATCH] Discard previews on IFC save + harden preview-active gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Save-path: * SaveProject._execute (project/operator.py) now calls preview_base.discard_pending_previews(context.scene) right after tool.Parametric.commit_pending_edits(). Previews are session- transient — discard rather than commit. Sibling gizmo polls gate on each preview's is_active flag; a stuck flag persisted through the save would silently hide them on reload. Mirrors the pattern already in gizmos-8088. Preview-active gate hardening: * preview_base.get_preview_props tolerates contexts without a ``scene`` attribute. Pre-existing tests use SimpleNamespace mocks for the context; the previous getattr(context.scene, ...) raised AttributeError before the inner default kicked in. Test update: * test_wall_header_refresh.test_geom_generation_invalidates_wall_geom_cache patches tool.Wall.read_geometry instead of the now-deleted local wall._read_wall_geometry (commit 7e5e7b8d6 migrated the call site). Generated with the assistance of an AI coding tool. --- src/bonsai/bonsai/bim/module/model/preview_base.py | 8 ++++++-- src/bonsai/bonsai/bim/module/project/operator.py | 5 +++++ .../test/bim/module/model/test_wall_header_refresh.py | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/preview_base.py b/src/bonsai/bonsai/bim/module/model/preview_base.py index b58e6ea246..13cde4e68a 100644 --- a/src/bonsai/bonsai/bim/module/model/preview_base.py +++ b/src/bonsai/bonsai/bim/module/model/preview_base.py @@ -60,8 +60,12 @@ def get_preview_props(context: bpy.types.Context, attr: str): Returns ``None`` if the umbrella isn't attached yet — true briefly during addon register and during plug-out, so polls / draw callbacks must defend against ``None`` rather than assuming the prop is always - available.""" - preview = getattr(context.scene, "BIMPreviewProperties", None) + available. Also tolerates contexts without a ``scene`` attribute + (test mocks built from ``SimpleNamespace``).""" + scene = getattr(context, "scene", None) + if scene is None: + return None + preview = getattr(scene, "BIMPreviewProperties", None) return getattr(preview, attr, None) if preview is not None else None diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index b9201871a4..1699bd0716 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -63,6 +63,7 @@ import bonsai.core.project as core import bonsai.tool as tool from bonsai.bim import export_ifc, import_ifc from bonsai.bim.ifc import IfcStore +from bonsai.bim.module.model import preview_base from bonsai.bim.module.model.decorator import FaceAreaDecorator, PolylineDecorator from bonsai.bim.module.model.polyline import PolylineOperator from bonsai.bim.module.project.data import LinksData, ProjectLibraryData @@ -1935,6 +1936,10 @@ class ExportIFC(bpy.types.Operator, ExportHelper): def _execute(self, context): committed, failed_commits = tool.Parametric.commit_pending_edits() + # Previews are session-transient — discard rather than commit. Sibling + # gizmo polls gate on each preview's is_active flag, and a stuck flag + # persisted through the save would silently hide them on reload. + preview_base.discard_pending_previews(context.scene) # Suffix is appended to the IFC save-success report below so the auto-commit # info isn't immediately overwritten by the success message in Blender's # status bar (only the latest self.report({"INFO"}, ...) sticks). diff --git a/src/bonsai/test/bim/module/model/test_wall_header_refresh.py b/src/bonsai/test/bim/module/model/test_wall_header_refresh.py index 933fab2454..41b8719ec5 100644 --- a/src/bonsai/test/bim/module/model/test_wall_header_refresh.py +++ b/src/bonsai/test/bim/module/model/test_wall_header_refresh.py @@ -75,7 +75,7 @@ def test_geom_generation_invalidates_wall_geom_cache(): sentinel_a = {"length": 1.0, "height": 2.0, "x_angle": 0.0} sentinel_b = {"length": 1.5, "height": 2.5, "x_angle": 0.0} - with patch.object(wall_mod, "_read_wall_geometry", side_effect=[sentinel_a, sentinel_b]): + with patch.object(tool.Wall, "read_geometry", side_effect=[sentinel_a, sentinel_b]): first = wall_mod._get_wall_geom_cached(group, fake_obj) assert first is sentinel_a # Same call without a generation bump must hit the cache (no extra read).