Closes #7765: Default IFC file path to relative and auto-convert on blend save

Previously, saving an IFC file stored an absolute path in `bim_props.ifc_file` by default. Users had to manually check "Use Relative Path" each time. This made projects less portable — moving or sharing a project folder broke the stored path, requiring manual relinking.

What changed:

-   `use_relative_project_path` now defaults to `True` in `BIMProjectProperties`, `LoadProject`, `ExportIFC`, and `SelectIfcFile`. Relative paths are opt-out rather than opt-in.

-   Added a `save_post` handler in `handler.py` (registered in `__init__.py`) that fires whenever the user saves the `.blend` file. If `use_relative_project_path` is enabled and the stored `ifc_file` path is still absolute, it converts it to a path relative to the blend file's directory. `IfcStore.path` is kept absolute internally so file loading continues to work correctly.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Ryan Schultz
2026-03-14 08:40:51 -05:00
parent 93c6350e0f
commit 1ccd74e78a
5 changed files with 25 additions and 4 deletions
+2
View File
@@ -271,6 +271,7 @@ def register():
parametric_lifecycle.install_parametric_lifecycle_handlers()
bpy.app.handlers.load_post.append(handler.load_post)
bpy.app.handlers.load_post.append(handler.loadIfcStore)
bpy.app.handlers.save_post.append(handler.save_post)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Scene.BIMSnapProperties = bpy.props.PointerProperty(type=prop.BIMSnapProperties)
bpy.types.Scene.BIMSnapGroups = bpy.props.PointerProperty(type=prop.BIMSnapGroups)
@@ -329,6 +330,7 @@ def unregister():
parametric_lifecycle.uninstall_parametric_lifecycle_handlers()
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
bpy.app.handlers.save_post.remove(handler.save_post)
del bpy.types.Scene.BIMProperties
del bpy.types.Collection.BIMCollectionProperties
del bpy.types.Object.BIMObjectProperties
+19
View File
@@ -432,6 +432,25 @@ def subscribe_to_viewport_shading_changes():
)
@persistent
def save_post(scene) -> None:
"""After saving the .blend file, convert the stored IFC path to relative if enabled."""
pprops = tool.Project.get_project_props()
if not pprops.use_relative_project_path:
return
bim_props = tool.Blender.get_bim_props()
ifc_path = bim_props.ifc_file
if not ifc_path or not os.path.isabs(ifc_path):
return
blend_dir = bpy.path.abspath("//")
if not blend_dir:
return
from bonsai.bim.ifc import IfcStore
rel_path = os.path.relpath(ifc_path, blend_dir)
bim_props.ifc_file = rel_path
IfcStore.set_path(ifc_path) # keep IfcStore.path absolute for loading
def _apply_save_file_invariants(scene: bpy.types.Scene) -> None:
"""Invariants enforced on every load_post: msgbus subscription, IFC owner
settings, scene-bound caches, load-transient parametric state, and the
@@ -964,7 +964,7 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
use_relative_path: bpy.props.BoolProperty(
name="Use Relative Path",
description="Store the IFC project path relative to the .blend file. Requires .blend file to be saved",
default=False,
default=True,
)
should_start_fresh_session: bpy.props.BoolProperty(
name="Should Start Fresh Session",
@@ -1875,7 +1875,7 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version")
json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False)
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
if TYPE_CHECKING:
filter_glob: str
+1 -1
View File
@@ -444,7 +444,7 @@ class BIMProjectProperties(PropertyGroup):
items=get_parent_libaries,
)
use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False)
use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=True)
should_save_metadata_for_this_file: BoolProperty(
name="Save Session Data for This File",
description="Enable saving session data (window layout, settings) to a metadata blend file for this specific IFC file",
+1 -1
View File
@@ -219,7 +219,7 @@ class SelectIfcFile(bpy.types.Operator, IFCFileSelector, ImportHelper):
bl_options = {"REGISTER", "UNDO"}
bl_description = f"Select a different IFC file.\n{tool.Blender.operator_invoke_filepath_hotkeys_description}"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
filename_ext = ".ifc"
def execute(self, context):