diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py index 38c7990653..f33c90fc6f 100644 --- a/src/bonsai/bonsai/bim/handler.py +++ b/src/bonsai/bonsai/bim/handler.py @@ -34,7 +34,11 @@ from mathutils import Vector import bonsai.bim import bonsai.core.model as core_model import bonsai.tool as tool -from bonsai.bim.ifc import IfcStore +from bonsai.bim.decorator_cache import ( + install_decorator_cache_handlers, + uninstall_decorator_cache_handlers, +) +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.data import AuthoringData @@ -43,6 +47,7 @@ from bonsai.bim.module.model.decorator import ( SlabDirectionDecorator, WallAxisDecorator, ) +from bonsai.bim.module.model.preview_base import discard_pending_previews from bonsai.bim.module.nest.decorator import NestDecorator cwd = os.path.dirname(os.path.realpath(__file__)) @@ -378,8 +383,10 @@ def subscribe_to_viewport_shading_changes(): ) -@persistent -def load_post(scene): +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.""" global global_subscription_owner active_object_key = bpy.types.LayerObjects, "active" bpy.msgbus.subscribe_rna( @@ -390,6 +397,24 @@ def load_post(scene): ifcopenshell.api.owner.settings.get_application = get_application AuthoringData.type_thumbnails = {} + tool.Parametric.heal_stale_edit_flags() + discard_pending_previews(scene) + + if tool.Ifc.get() and bpy.data.is_saved: + props = tool.Blender.get_bim_props() + props.has_blend_warning = True + + # Probe the H5 cooked-geometry cache so the multi-instance warning surfaces + # right after .blend load. Without this, the lock is only detected when a + # mutation triggers ``clear_cache`` — by which time the user has already + # made changes that may now conflict with the other Blender instance. + if tool.Ifc.get(): + get_cache_or_detect_lock() + + +def _apply_user_preferences() -> None: + """User-preference-driven UI setup: toolbar, BIM workspace, viewport shading + subscription, scene-panel hijack, tab layout, snap defaults.""" preferences = tool.Blender.get_addon_preferences() if not preferences.should_setup_toolbar: tool.Blender.unregister_toolbar() @@ -413,11 +438,21 @@ def load_post(scene): tool.Blender.override_scene_panel(panel) tool.Blender.setup_tabs() - if tool.Ifc.get() and bpy.data.is_saved: - props = tool.Blender.get_bim_props() - props.has_blend_warning = True + if preferences.should_use_snap and (scene := bpy.context.scene): + # Snapping is off by default in Blender, but in BIM, it's more useful to be on + scene.tool_settings.use_snap = True + # Match default Bonsai snaps + scene.tool_settings.snap_elements_base = {"EDGE", "EDGE_PERPENDICULAR", "VERTEX", "EDGE_MIDPOINT", "FACE"} - # Bonsai overlays + tool.Blender.sync_old_preferences() + + +def _install_viewport_overlays() -> None: + """Sync every Bonsai viewport decorator to its enabled state. + + Wrapped in uninstall/install of the decorator-cache bump handlers so a + decorator's own install path doesn't double-bind to depsgraph_update_post + via ``TokenCache`` instances created during their own ``install()``.""" georeference_props = tool.Georeference.get_georeference_props() aggregate_props = tool.Aggregate.get_aggregate_props() nest_props = tool.Nest.get_nest_props() @@ -427,23 +462,26 @@ def load_post(scene): NestDecorator.uninstall() WallAxisDecorator.uninstall() SlabDirectionDecorator.uninstall() - if georeference_props.should_visualise: - GeoreferenceDecorator.install(bpy.context) - if aggregate_props.aggregate_decorator: - AggregateDecorator.install(bpy.context) - if nest_props.nest_decorator: - NestDecorator.install(bpy.context) - if model_props.show_wall_axis: - WallAxisDecorator.install(bpy.context) - if model_props.show_slab_direction: - SlabDirectionDecorator.install(bpy.context) - if model_props.show_bounding_box: - BoundingBoxDecorator.install(bpy.context) + uninstall_decorator_cache_handlers() + try: + if georeference_props.should_visualise: + GeoreferenceDecorator.install(bpy.context) + if aggregate_props.aggregate_decorator: + AggregateDecorator.install(bpy.context) + if nest_props.nest_decorator: + NestDecorator.install(bpy.context) + if model_props.show_wall_axis: + WallAxisDecorator.install(bpy.context) + if model_props.show_slab_direction: + SlabDirectionDecorator.install(bpy.context) + if model_props.show_bounding_box: + BoundingBoxDecorator.install(bpy.context) + finally: + install_decorator_cache_handlers() - if preferences.should_use_snap and (scene := bpy.context.scene): - # Snapping is off by default in Blender, but in BIM, it's more useful to be on - scene.tool_settings.use_snap = True - # Match default Bonsai snaps - scene.tool_settings.snap_elements_base = {"EDGE", "EDGE_PERPENDICULAR", "VERTEX", "EDGE_MIDPOINT", "FACE"} - tool.Blender.sync_old_preferences() +@persistent +def load_post(scene): + _apply_save_file_invariants(scene) + _apply_user_preferences() + _install_viewport_overlays()