diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index c0dd8c70c1..a549f7d69e 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -1062,18 +1062,34 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper): and not self.is_advanced ): filepath = self.get_filepath() - suffix = tool.Blender.get_addon_preferences().metadata_blend_file_suffix - if str(filepath).lower().endswith(".ifc"): - metadata_path = Path(str(filepath)[:-4] + suffix) - else: - metadata_path = Path(str(filepath) + suffix) - if metadata_path.exists() and metadata_path.is_file(): - try: - bpy.ops.bim.load_blend_metadata_and_ifc(filepath=filepath) - self.report({"INFO"}, f"Loaded metadata file: {metadata_path.name}") - return {"FINISHED"} - except Exception as e: - self.report({"WARNING"}, f"Failed to load metadata file, using regular load: {e}") + + # First, load the IFC file temporarily to check for metadata document + temp_ifc = None + has_metadata_doc = False + try: + temp_ifc = ifcopenshell.open(str(filepath)) + for doc in temp_ifc.by_type("IfcDocumentInformation"): + if getattr(doc, "Scope", None) == "BLEND_METADATA": + has_metadata_doc = True + break + except: + pass + finally: + temp_ifc = None + + if has_metadata_doc: + suffix = tool.Blender.get_addon_preferences().metadata_blend_file_suffix + if str(filepath).lower().endswith(".ifc"): + metadata_path = Path(str(filepath)[:-4] + suffix) + else: + metadata_path = Path(str(filepath) + suffix) + if metadata_path.exists() and metadata_path.is_file(): + try: + bpy.ops.bim.load_blend_metadata_and_ifc(filepath=filepath) + self.report({"INFO"}, f"Loaded metadata file: {metadata_path.name}") + return {"FINISHED"} + except Exception as e: + self.report({"WARNING"}, f"Failed to load metadata file, using regular load: {e}") @persistent def load_handler(*args): @@ -1121,6 +1137,10 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper): props.is_loading = True props.total_elements = len(tool.Ifc.get().by_type("IfcElement")) props.use_relative_project_path = self.use_relative_path + + metadata_doc = tool.Project.get_metadata_document_information() + props.should_save_metadata_for_this_file = metadata_doc is not None + tool.Blender.register_toolbar() tool.Project.add_recent_ifc_project(self.get_filepath_abs()) @@ -1749,6 +1769,22 @@ class ExportIFC(bpy.types.Operator, ExportHelper): settings.json_version = self.json_version settings.json_compact = self.json_compact + pprops = tool.Project.get_project_props() + if tool.Blender.get_addon_preferences().save_metadata_blend_file and pprops.should_save_metadata_for_this_file: + suffix = tool.Blender.get_addon_preferences().metadata_blend_file_suffix + if output_file.lower().endswith(".ifc"): + metadata_filename = os.path.basename(output_file)[:-4] + suffix + else: + metadata_filename = os.path.basename(output_file) + suffix + + if not tool.Project.get_metadata_document_information(): + tool.Project.create_metadata_document_information(metadata_filename) + else: + tool.Project.update_metadata_document_information(metadata_filename) + else: + if not pprops.should_save_metadata_for_this_file: + tool.Project.remove_metadata_document_information() + ifc_exporter = export_ifc.IfcExporter(settings) print("Starting export") settings.logger.info("Starting export") @@ -1765,7 +1801,8 @@ class ExportIFC(bpy.types.Operator, ExportHelper): tool.Ifc.set_path(output_file) bim_props.is_dirty = False - if tool.Blender.get_addon_preferences().save_metadata_blend_file: + pprops = tool.Project.get_project_props() + if tool.Blender.get_addon_preferences().save_metadata_blend_file and pprops.should_save_metadata_for_this_file: try: bpy.ops.bim.save_blend_metadata_file() suffix = tool.Blender.get_addon_preferences().metadata_blend_file_suffix diff --git a/src/bonsai/bonsai/bim/module/project/prop.py b/src/bonsai/bonsai/bim/module/project/prop.py index 2cbdeb6e7b..8983c927c9 100644 --- a/src/bonsai/bonsai/bim/module/project/prop.py +++ b/src/bonsai/bonsai/bim/module/project/prop.py @@ -411,6 +411,11 @@ class BIMProjectProperties(PropertyGroup): ) use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False) + 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", + default=False, + ) queried_obj: bpy.props.PointerProperty(type=bpy.types.Object) queried_obj_root: bpy.props.PointerProperty(type=bpy.types.Object) clipping_planes: bpy.props.CollectionProperty(type=ObjProperty) @@ -504,6 +509,7 @@ class BIMProjectProperties(PropertyGroup): parent_library: str use_relative_project_path: bool + should_save_metadata_for_this_file: bool queried_obj: Union[bpy.types.Object, None] queried_obj_root: Union[bpy.types.Object, None] clipping_planes: bpy.types.bpy_prop_collection_idprop[ObjProperty] diff --git a/src/bonsai/bonsai/bim/module/project/ui.py b/src/bonsai/bonsai/bim/module/project/ui.py index 5d4f3f695b..1a6f727b20 100644 --- a/src/bonsai/bonsai/bim/module/project/ui.py +++ b/src/bonsai/bonsai/bim/module/project/ui.py @@ -338,9 +338,9 @@ class BIM_PT_project(Panel): else: metadata_filename = os.path.basename(props.ifc_file) + suffix row = self.layout.row(align=True) - col = row.column() - col.enabled = False - col.label(text=f"Saving session data to: {metadata_filename}") + row.use_property_split = False + pprops = tool.Project.get_project_props() + row.prop(pprops, "should_save_metadata_for_this_file", text=f"Save session data to: {metadata_filename}") class BIM_PT_new_project_wizard(Panel): diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py index 0bfb4b00c1..fec4f6a42b 100644 --- a/src/bonsai/bonsai/tool/project.py +++ b/src/bonsai/bonsai/tool/project.py @@ -513,3 +513,60 @@ class Project(bonsai.core.tool.Project): if tmp.exists(): shutil.rmtree(tmp) bpy.ops.bim.save_project(filepath=cls.TEMP_PROJECT_PATH.__str__(), should_save_as=True) + + @classmethod + def get_metadata_document_information(cls) -> Optional[ifcopenshell.entity_instance]: + ifc_file = tool.Ifc.get() + if not ifc_file: + return None + for doc in ifc_file.by_type("IfcDocumentInformation"): + if getattr(doc, "Scope", None) == "BLEND_METADATA": + return doc + return None + + @classmethod + def create_metadata_document_information(cls, metadata_filename: str) -> ifcopenshell.entity_instance: + ifc_file = tool.Ifc.get() + if not ifc_file: + raise Exception("No IFC file loaded") + + doc = tool.Ifc.run("document.add_information", parent=None) + + if ifc_file.schema == "IFC2X3": + tool.Ifc.run("document.edit_information", information=doc, attributes={ + "DocumentId": "BLEND_METADATA", + "Name": "Blend Metadata", + "Scope": "BLEND_METADATA", + "Description": "References to blend metadata file for this IFC project", + "Location": metadata_filename + }) + else: + tool.Ifc.run("document.edit_information", information=doc, attributes={ + "Identification": "BLEND_METADATA", + "Name": "Blend Metadata", + "Scope": "BLEND_METADATA", + "Description": "References to blend metadata file for this IFC project", + "Location": metadata_filename + }) + + return doc + + @classmethod + def update_metadata_document_information(cls, metadata_filename: str) -> None: + doc = cls.get_metadata_document_information() + if not doc: + return + + ifc_file = tool.Ifc.get() + if not ifc_file: + return + + tool.Ifc.run("document.edit_information", information=doc, attributes={ + "Location": metadata_filename + }) + + @classmethod + def remove_metadata_document_information(cls) -> None: + doc = cls.get_metadata_document_information() + if doc: + tool.Ifc.run("document.remove_information", information=doc)