From 1da48061e3adac40df38e851196ae1fc75aa2367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Sat, 5 Sep 2026 23:53:59 -0300 Subject: [PATCH] snap: fix stale cut geometry after file reload --- .../bonsai/bim/module/drawing/handler.py | 3 +++ src/bonsai/bonsai/tool/raycast.py | 25 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/handler.py b/src/bonsai/bonsai/bim/module/drawing/handler.py index aac48b9479..49bdfac23b 100644 --- a/src/bonsai/bonsai/bim/module/drawing/handler.py +++ b/src/bonsai/bonsai/bim/module/drawing/handler.py @@ -31,6 +31,9 @@ def load_post(*args): else: decoration.DecorationsHandler.uninstall() + # CutDecorator is drawing-view-only state and must not survive a new file. + decoration.CutDecorator.uninstall() + @persistent def depsgraph_update_pre_handler(scene): diff --git a/src/bonsai/bonsai/tool/raycast.py b/src/bonsai/bonsai/tool/raycast.py index bf55f37c7b..892f9688ae 100644 --- a/src/bonsai/bonsai/tool/raycast.py +++ b/src/bonsai/bonsai/tool/raycast.py @@ -39,13 +39,15 @@ from mathutils import Matrix, Vector import bonsai.core.tool import bonsai.tool as tool +from bonsai.bim.decorator_cache import get_decorator_cache_token from bonsai.bim.module.drawing.data import DecoratorData from bonsai.bim.module.drawing.decoration import CutDecorator -_wireframe_batch_cache: dict[int, dict[str, tuple[GPUBatch, int, list]]] = {} +_wireframe_batch_cache: dict[tuple[int, int], dict[str, tuple[GPUBatch, int, list]]] = {} _wireframe_vert_fmt: GPUVertFormat | None = None -_triangle_batch_cache: dict[int, tuple[GPUBatch, int]] = {} +_triangle_batch_cache: dict[tuple[int, int], tuple[GPUBatch, int]] = {} _triangle_vert_fmt: GPUVertFormat | None = None +_last_decorator_cache_token: int | None = None _encoding_shader: gpu.types.GPUShader | None = None _offscreen: GPUOffScreen | None = None _obj_list: list[[bpy.types.Object, bool]] = [] @@ -101,6 +103,16 @@ def _create_vert_format() -> GPUVertFormat: return fmt +def _discard_stale_batches_if_token_changed() -> None: + """Clear GPU batches when the decorator cache token is bumped on file load, undo, redo, or depsgraph changes.""" + global _last_decorator_cache_token + token = get_decorator_cache_token() + if token != _last_decorator_cache_token: + _triangle_batch_cache.clear() + _wireframe_batch_cache.clear() + _last_decorator_cache_token = token + + def _find_closest_wireframe_pixel(buffer_data, cx, cy): """Scan *buffer_data* (list of rows) for the closest non-zero pixel to (cx, cy). Used for points a lines detection. Returns ``(encoded_value, dx, dy)`` or None.""" @@ -203,7 +215,8 @@ def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch | None, bo if _triangle_vert_fmt is None: _triangle_vert_fmt = _create_vert_format() - cache_key = id(obj) + _discard_stale_batches_if_token_changed() + cache_key = (obj.session_uid, get_decorator_cache_token()) if cache_key in _triangle_batch_cache: return _triangle_batch_cache[cache_key] @@ -355,7 +368,8 @@ def _ensure_wireframe_batches(obj: bpy.types.Object) -> dict[str, tuple[GPUBatch if _wireframe_vert_fmt is None: _wireframe_vert_fmt = _create_vert_format() - cache_key = id(obj) + _discard_stale_batches_if_token_changed() + cache_key = (obj.session_uid, get_decorator_cache_token()) # Cache hit if cache_key in _wireframe_batch_cache: @@ -1199,7 +1213,7 @@ class Raycast(bonsai.core.tool.Raycast): @classmethod def clear_cache(cls): - global _wireframe_batch_cache, _wireframe_vert_fmt, _triangle_batch_cache, _triangle_vert_fmt, _encoding_shader, _offscreen, _obj_list + global _wireframe_batch_cache, _wireframe_vert_fmt, _triangle_batch_cache, _triangle_vert_fmt, _encoding_shader, _offscreen, _obj_list, _last_decorator_cache_token _wireframe_batch_cache = {} _wireframe_vert_fmt = None _triangle_batch_cache = {} @@ -1207,6 +1221,7 @@ class Raycast(bonsai.core.tool.Raycast): _encoding_shader = None _offscreen = None _obj_list = [] + _last_decorator_cache_token = None @classmethod def ray_cast_by_proximity(