From e6e6bd841cd0da37c34e82b627cc5d29fc8112ed Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 30 Sep 2024 12:09:13 +0500 Subject: [PATCH] project.operator - use pathlib --- .../bonsai/bim/module/project/operator.py | 98 +++++++++---------- src/bonsai/bonsai/bim/module/project/prop.py | 5 +- src/bonsai/bonsai/tool/blender.py | 6 ++ 3 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index f89e9f90ad..53d42f7b6e 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -54,7 +54,7 @@ from bonsai.bim.module.project.data import LinksData from bonsai.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator, MeasureDecorator from bonsai.bim.module.model.decorator import PolylineDecorator from bonsai.bim.module.model.polyline import PolylineOperator -from typing import Union +from typing import Union, TYPE_CHECKING class NewProject(bpy.types.Operator): @@ -933,6 +933,11 @@ class LinkIfc(bpy.types.Operator): use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False) use_cache: bpy.props.BoolProperty(name="Use Cache", default=True) + if TYPE_CHECKING: + filepath: str + files: list[bpy.types.OperatorFileListElement] + directory: str + def draw(self, context): pprops = context.scene.BIMProjectProperties row = self.layout.row() @@ -949,20 +954,20 @@ class LinkIfc(bpy.types.Operator): def execute(self, context): start = time.time() - files = [f.name.replace("\\", "/") for f in self.files] if self.files else [self.filepath.replace("\\", "/")] + files = [f.name for f in self.files] if self.files else [self.filepath] for filename in files: - filepath = os.path.join(self.directory, filename).replace("\\", "/") - if bpy.data.filepath and Path(filepath).samefile(bpy.data.filepath): + filepath = Path(self.directory) / filename + if bpy.data.filepath and filepath.samefile(bpy.data.filepath): self.report({"INFO"}, "Can't link the current .blend file") continue new = context.scene.BIMProjectProperties.links.add() if self.use_relative_path: try: - filepath = os.path.relpath(filepath, bpy.path.abspath("//")).replace("\\", "/") + filepath = filepath.relative_to(bpy.path.abspath("//")) except: pass # Perhaps on another drive or something - new.name = filepath - status = bpy.ops.bim.load_link(filepath=filepath, use_cache=self.use_cache) + new.name = filepath.as_posix() + status = bpy.ops.bim.load_link(filepath=filepath.as_posix(), use_cache=self.use_cache) if status == {"CANCELLED"}: error_msg = ( f'Error processing IFC file "{self.filepath}" ' @@ -988,9 +993,9 @@ class UnlinkIfc(bpy.types.Operator): filepath: bpy.props.StringProperty() def execute(self, context): - self.filepath = self.filepath.replace("\\", "/") - bpy.ops.bim.unload_link(filepath=self.filepath) - index = context.scene.BIMProjectProperties.links.find(self.filepath) + filepath = Path(self.filepath).as_posix() + bpy.ops.bim.unload_link(filepath=filepath) + index = context.scene.BIMProjectProperties.links.find(filepath) if index != -1: context.scene.BIMProjectProperties.links.remove(index) return {"FINISHED"} @@ -1004,11 +1009,7 @@ class UnloadLink(bpy.types.Operator): filepath: bpy.props.StringProperty() def execute(self, context): - self.filepath = self.filepath.replace("\\", "/") - filepath = self.filepath - if not os.path.isabs(filepath): - filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath)) - filepath = Path(filepath) + filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.filepath)) if filepath.suffix.lower() == ".ifc": filepath = filepath.with_suffix(".ifc.cache.blend") @@ -1046,25 +1047,25 @@ class LoadLink(bpy.types.Operator): filepath: bpy.props.StringProperty() use_cache: bpy.props.BoolProperty(name="Use Cache", default=True) + filepath_: Path + def execute(self, context): - self.filepath = self.filepath.replace("\\", "/") - filepath = self.filepath - if not os.path.isabs(filepath): - filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath)).replace("\\", "/") - if self.filepath.lower().endswith(".blend"): + filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.filepath)) + self.filepath_ = filepath + if filepath.suffix.lower().endswith(".blend"): self.link_blend(filepath) - elif self.filepath.lower().endswith(".ifc"): + elif filepath.suffix.lower().endswith(".ifc"): status = self.link_ifc() if status: return status return {"FINISHED"} - def link_blend(self, filepath: str) -> None: - with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to): + def link_blend(self, filepath: Path) -> None: + with bpy.data.libraries.load(str(filepath), link=True) as (data_from, data_to): data_to.scenes = data_from.scenes - link = bpy.context.scene.BIMProjectProperties.links.get(self.filepath) + link = bpy.context.scene.BIMProjectProperties.links.get(self.filepath_.as_posix()) for scene in bpy.data.scenes: - if not scene.library or scene.library.filepath.replace("\\", "/") != filepath: + if not scene.library or Path(scene.library.filepath) != filepath: continue for child in scene.collection.children: if "IfcProject" not in child.name: @@ -1080,13 +1081,13 @@ class LoadLink(bpy.types.Operator): tool.Blender.select_and_activate_single_object(bpy.context, empty) def link_ifc(self) -> Union[set[str], None]: - blend_filepath = self.filepath + ".cache.blend" - h5_filepath = self.filepath + ".cache.h5" + blend_filepath = self.filepath_.with_suffix(".ifc.cache.blend") + h5_filepath = self.filepath_.with_suffix(".ifc.cache.h5") - if not self.use_cache and os.path.exists(blend_filepath): + if not self.use_cache and blend_filepath.exists(): os.remove(blend_filepath) - if not os.path.exists(blend_filepath): + if not blend_filepath.exists(): pprops = bpy.context.scene.BIMProjectProperties gprops = bpy.context.scene.BIMGeoreferenceProperties @@ -1111,7 +1112,7 @@ def run(): pprops.false_origin = "{pprops.false_origin}" pprops.project_north = "{pprops.project_north}" bpy.ops.bim.load_linked_project(filepath="{self.filepath}") - bpy.ops.wm.save_as_mainfile(filepath="{blend_filepath}") + bpy.ops.wm.save_as_mainfile(filepath="{blend_filepath.as_posix()}") try: run() @@ -1127,19 +1128,19 @@ except Exception as e: run = subprocess.run([bpy.app.binary_path, "-b", "--python", temp_file.name, "--python-exit-code", "1"]) if run.returncode == 1: print("An error occurred while processing your IFC.") - if not os.path.exists(blend_filepath) or os.stat(blend_filepath).st_mtime < t: + if not blend_filepath.exists() or blend_filepath.stat().st_mtime < t: return {"CANCELLED"} self.set_model_origin_from_link() self.link_blend(blend_filepath) - def set_model_origin_from_link(self): + def set_model_origin_from_link(self) -> None: if tool.Ifc.get(): return # The current model's coordinates always take priority. - json_filepath = self.filepath + ".cache.json" - if not os.path.exists(json_filepath): + json_filepath = self.filepath_.with_suffix(".ifc.cache.json") + if not json_filepath.exists(): return with open(json_filepath, "r") as f: @@ -1159,15 +1160,16 @@ class ReloadLink(bpy.types.Operator): filepath: bpy.props.StringProperty() def execute(self, context): - def get_linked_ifcs(): - selected_filename = os.path.basename(self.filepath.replace("\\", "/")) - return [ + filepath = Path(self.filepath) + + def get_linked_ifcs() -> set[bpy.types.Library]: + return { c.library for c in bpy.data.collections - if "IfcProject" in c.name and c.library and os.path.basename(c.library.filepath) == selected_filename - ] + if "IfcProject" in c.name and c.library and Path(c.library.filepath) == filepath + } - for library in get_linked_ifcs() or []: + for library in get_linked_ifcs(): library.reload() return {"FINISHED"} @@ -1182,9 +1184,7 @@ class ToggleLinkSelectability(bpy.types.Operator): def execute(self, context): props = context.scene.BIMProjectProperties link = props.links.get(self.link) - if not os.path.isabs(self.link): - self.link = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.link)) - self.library_filepath = Path(self.link).with_suffix(".ifc.cache.blend") + self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend")) for collection in self.get_linked_collections(): collection.hide_select = not collection.hide_select link.is_selectable = not collection.hide_select @@ -1209,9 +1209,7 @@ class ToggleLinkVisibility(bpy.types.Operator): def execute(self, context): props = context.scene.BIMProjectProperties link = props.links.get(self.link) - if not os.path.isabs(self.link): - self.link = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.link)) - self.library_filepath = Path(self.link).with_suffix(".ifc.cache.blend") + self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend")) if self.mode == "WIREFRAME": self.toggle_wireframe(link) elif self.mode == "VISIBLE": @@ -1298,10 +1296,8 @@ class ExportIFCBase: return {"FINISHED"} self.save_as_invoked = False - if context.scene.BIMProperties.ifc_file and not self.should_save_as: - self.filepath = context.scene.BIMProperties.ifc_file - if not os.path.isabs(self.filepath): - self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)) + if (filepath := context.scene.BIMProperties.ifc_file) and not self.should_save_as: + self.filepath = str(tool.Blender.ensure_blender_path_is_abs(Path(filepath))) return self.execute(context) if not self.filepath: if bpy.data.is_saved: @@ -1408,7 +1404,7 @@ class LoadLinkedProject(bpy.types.Operator): pprops = bpy.context.scene.BIMProjectProperties gprops = bpy.context.scene.BIMGeoreferenceProperties - self.filepath = self.filepath.replace("\\", "/") + self.filepath = Path(self.filepath).as_posix() print("Processing", self.filepath) self.collection = bpy.data.collections.new("IfcProject/" + os.path.basename(self.filepath)) diff --git a/src/bonsai/bonsai/bim/module/project/prop.py b/src/bonsai/bonsai/bim/module/project/prop.py index 0f9751df87..45db19c547 100644 --- a/src/bonsai/bonsai/bim/module/project/prop.py +++ b/src/bonsai/bonsai/bim/module/project/prop.py @@ -106,7 +106,10 @@ class FilterCategory(PropertyGroup): class Link(PropertyGroup): - name: StringProperty(name="Name") + name: StringProperty( + name="Name", + description="Filepath to linked .ifc file, stored in posix format (could be relative to .blend file, not to .ifc)", + ) is_loaded: BoolProperty(name="Is Loaded", default=False) is_selectable: BoolProperty(name="Is Selectable", default=True) is_wireframe: BoolProperty(name="Is Wireframe", default=False) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 6ae5fabdfa..1bca5a3548 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -414,6 +414,12 @@ class Blender(bonsai.core.tool.Blender): return path.as_posix() + @classmethod + def ensure_blender_path_is_abs(cls, blender_path: Path) -> Path: + if blender_path.is_absolute(): + return blender_path + return bpy.path.abspath("//") / blender_path + @classmethod def get_default_selection_keypmap(cls) -> tuple: """keymap to replicate default blender selection behaviour with click and box selection"""