From 5e930972d669af3e2270ea81aafa0122db201029 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 20 Jan 2025 18:46:53 +0500 Subject: [PATCH] User 'data' folder #5564 Bonsai now creates a user data folder that allows you overriding Bonsai data files. E.g. you can put you own `default.css` as `data/assets/default.css` in that folder and it will override the `default.css` Bonsai is using for the drawings. Previously you could have change internal `default.css` but Bonsai would always restore the original one on every update / reinstallation. You can also store your .ifc files in data/libraries or data/templates and Bonsai will ensure they're loaded and you won't need to worry about Bonsai removing them later. E.g. on Windows this path is `C:\Users\xxx\AppData\Roaming\bonsai\bonsai\data`/ You can also find path in the preferences - https://i.imgur.com/kwMc0JU.png --- src/bonsai/bonsai/bim/module/bcf/operator.py | 2 +- .../bonsai/bim/module/clash/operator.py | 2 +- .../bonsai/bim/module/drawing/annotation.py | 3 +- src/bonsai/bonsai/bim/module/drawing/data.py | 3 +- .../bonsai/bim/module/drawing/decoration.py | 7 +++-- .../bonsai/bim/module/drawing/operator.py | 9 +----- .../bonsai/bim/module/drawing/scheduler.py | 3 +- .../bonsai/bim/module/drawing/sheeter.py | 12 +++++--- .../bonsai/bim/module/drawing/svgwriter.py | 3 +- .../bonsai/bim/module/patch/operator.py | 2 +- src/bonsai/bonsai/bim/module/project/data.py | 10 ++----- .../bonsai/bim/module/project/operator.py | 8 ++--- src/bonsai/bonsai/bim/module/project/prop.py | 6 ++-- .../bonsai/bim/module/pset_template/data.py | 14 ++++----- .../bim/module/pset_template/operator.py | 4 ++- src/bonsai/bonsai/bim/operator.py | 4 +-- src/bonsai/bonsai/bim/prop.py | 4 ++- src/bonsai/bonsai/tool/blender.py | 30 ++++++++++++++++++- src/bonsai/bonsai/tool/cost.py | 5 ++-- src/bonsai/bonsai/tool/drawing.py | 6 ++-- src/bonsai/bonsai/tool/project.py | 4 +-- src/bonsai/bonsai/tool/web.py | 12 ++++---- 22 files changed, 86 insertions(+), 67 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/bcf/operator.py b/src/bonsai/bonsai/bim/module/bcf/operator.py index 0ff5b80d4e..0295d2cdde 100644 --- a/src/bonsai/bonsai/bim/module/bcf/operator.py +++ b/src/bonsai/bonsai/bim/module/bcf/operator.py @@ -616,7 +616,7 @@ class AddBcfViewpoint(bpy.types.Operator): old_file_format = blender_render.image_settings.file_format blender_render.image_settings.file_format = "PNG" old_filepath = blender_render.filepath - blender_render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png") + blender_render.filepath = tool.Blender.get_data_dir_path("snapshot.png").__str__() bpy.ops.render.opengl(write_still=True) with open(blender_render.filepath, "rb") as f: snapshot = f.read() diff --git a/src/bonsai/bonsai/bim/module/clash/operator.py b/src/bonsai/bonsai/bim/module/clash/operator.py index c51e27b423..020aeba55e 100644 --- a/src/bonsai/bonsai/bim/module/clash/operator.py +++ b/src/bonsai/bonsai/bim/module/clash/operator.py @@ -266,7 +266,7 @@ class ExecuteIfcClash(bpy.types.Operator): context.scene.render.resolution_x = 480 context.scene.render.resolution_y = 270 context.scene.render.image_settings.file_format = "PNG" - context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png") + context.scene.render.filepath = tool.Blender.get_data_dir_path("shapshot.png").__str__() bpy.ops.render.opengl(write_still=True) with open(context.scene.render.filepath, "rb") as f: return ("snapshot.png", f.read()) diff --git a/src/bonsai/bonsai/bim/module/drawing/annotation.py b/src/bonsai/bonsai/bim/module/drawing/annotation.py index 6b13782f8d..f2946b623b 100644 --- a/src/bonsai/bonsai/bim/module/drawing/annotation.py +++ b/src/bonsai/bonsai/bim/module/drawing/annotation.py @@ -22,6 +22,7 @@ import math import bmesh import bonsai.tool as tool import ifcopenshell.util.element +from pathlib import Path from mathutils import Vector, Matrix from typing import Optional @@ -43,7 +44,7 @@ class Annotator: font = bpy.data.fonts.get("OpenGost TypeB TT") if not font: font = bpy.data.fonts.load( - os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf") + tool.Blender.get_data_dir_path(Path("fonts") / "OpenGost Type B TT.ttf").__str__() ) font.name = "OpenGost Type B TT" obj.data.font = font diff --git a/src/bonsai/bonsai/bim/module/drawing/data.py b/src/bonsai/bonsai/bim/module/drawing/data.py index 7730dcde09..53108485c9 100644 --- a/src/bonsai/bonsai/bim/module/drawing/data.py +++ b/src/bonsai/bonsai/bim/module/drawing/data.py @@ -82,8 +82,7 @@ class SheetsData: @classmethod def titleblocks(cls): - files = Path(os.path.join(bpy.context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob("*.svg") - files = [str(f.stem) for f in files] + files = [p.stem for p in tool.Blender.get_data_dir_paths(Path("templates") / "titleblocks", "*.svg")] if tool.Ifc.get(): project = tool.Ifc.get().by_type("IfcProject")[0] diff --git a/src/bonsai/bonsai/bim/module/drawing/decoration.py b/src/bonsai/bonsai/bim/module/drawing/decoration.py index e4b3f702d4..f392fcbb47 100644 --- a/src/bonsai/bonsai/bim/module/drawing/decoration.py +++ b/src/bonsai/bonsai/bim/module/drawing/decoration.py @@ -28,6 +28,7 @@ import ifcopenshell.util.element import ifcopenshell.util.unit import bonsai.tool as tool import bonsai.bim.module.drawing.helper as helper +from pathlib import Path from math import pi, sin, cos, tan, acos, atan, degrees, radians, ceil from bpy.types import SpaceView3D from mathutils import Vector, Matrix @@ -2022,9 +2023,9 @@ class DecorationsHandler: self.decorators[object_type] = self.decorators["FALL"] self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"] if drawing_font := bpy.context.scene.DocProperties.drawing_font: - drawing_font_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", drawing_font) - if os.path.exists(drawing_font_path): - font_id = blf.load(drawing_font_path) + drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font) + if drawing_font_path.is_file(): + font_id = blf.load(drawing_font_path.__str__()) for decorator in self.decorators.values(): decorator.font_id = font_id diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index c71178f38a..d42e7122e9 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -276,7 +276,6 @@ class CreateDrawing(bpy.types.Operator): self.svg_writer = svgwriter.SvgWriter() self.svg_writer.human_scale = self.human_scale self.svg_writer.scale = self.scale - self.svg_writer.data_dir = context.scene.BIMProperties.data_dir self.svg_writer.camera = self.camera self.svg_writer.camera_width, self.svg_writer.camera_height = self.get_camera_dimensions() self.svg_writer.camera_projection = tuple( @@ -1667,7 +1666,6 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator): ) tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes) sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = context.scene.BIMProperties.data_dir sheet_builder.add_drawing(reference, drawing, sheet) tool.Drawing.import_sheets() @@ -1697,7 +1695,6 @@ class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator): sheet = tool.Drawing.get_reference_document(reference) sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = context.scene.BIMProperties.data_dir sheet_builder.remove_drawing(reference, sheet) tool.Ifc.run("document.remove_reference", reference=reference) @@ -1760,7 +1757,6 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator): name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0] sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = scene.BIMProperties.data_dir references = sheet_builder.build(sheet) raster_references = [tool.Ifc.get_relative_uri(r) for r in references["RASTER"]] @@ -2184,7 +2180,7 @@ class ReloadDrawingStyles(bpy.types.Operator): json_path = Path(tool.Ifc.resolve_uri(rel_path)) if not json_path.exists(): - ootb_resource = Path(context.scene.BIMProperties.data_dir) / "assets" / "shading_styles.json" + ootb_resource = tool.Blender.get_data_dir_path(Path("assets") / "shading_styles.json") print( f"WARNING. Couldn't find shading_styles for the drawing by the path: {json_path}. " f"Default BBIM resource will be copied from {ootb_resource}" @@ -2582,7 +2578,6 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator): tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes) sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = context.scene.BIMProperties.data_dir sheet_builder.add_document(reference, schedule, sheet) tool.Drawing.import_sheets() @@ -2649,7 +2644,6 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator): tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes) sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = context.scene.BIMProperties.data_dir sheet_builder.add_document(reference, extref, sheet) tool.Drawing.import_sheets() @@ -3079,7 +3073,6 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator): attributes={"Location": tool.Drawing.get_default_titleblock_path(titleblock)}, ) sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = context.scene.BIMProperties.data_dir sheet_builder.change_titleblock(sheet, titleblock) tool.Drawing.import_sheets() diff --git a/src/bonsai/bonsai/bim/module/drawing/scheduler.py b/src/bonsai/bonsai/bim/module/drawing/scheduler.py index 568c3ca294..d00fd1d94f 100644 --- a/src/bonsai/bonsai/bim/module/drawing/scheduler.py +++ b/src/bonsai/bonsai/bim/module/drawing/scheduler.py @@ -22,6 +22,7 @@ import bpy import string import svgwrite import openpyxl +import bonsai.tool as tool from bonsai.bim.module.drawing.svgwriter import SvgWriter from odf.opendocument import load as load_ods @@ -77,7 +78,7 @@ class Scheduler: ifc_file_path = os.path.dirname(IfcStore.path) stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path if not os.path.exists(stylesheet_path): - stylesheet_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", "schedule.css") + stylesheet_path = tool.Blender.get_data_dir_path(Path("assets") / "schedule.css") with open(stylesheet_path, "r") as stylesheet: css = stylesheet.read() diff --git a/src/bonsai/bonsai/bim/module/drawing/sheeter.py b/src/bonsai/bonsai/bim/module/drawing/sheeter.py index 69bd86aac4..bf8c2d76c7 100644 --- a/src/bonsai/bonsai/bim/module/drawing/sheeter.py +++ b/src/bonsai/bonsai/bim/module/drawing/sheeter.py @@ -26,6 +26,7 @@ import urllib.parse import xml.etree.ElementTree as ET import bonsai.tool as tool import ifcopenshell.util.geolocation +from pathlib import Path from xml.dom import minidom from mathutils import Vector import re @@ -38,7 +39,6 @@ XLINK = "{http://www.w3.org/1999/xlink}" class SheetBuilder: def __init__(self): - self.data_dir = None self.scale = "NTS" def create(self, layout_path: str, titleblock_name: str) -> None: @@ -49,7 +49,9 @@ class SheetBuilder: root.attrib["version"] = "1.1" sheet_dir = os.path.dirname(layout_path) - ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg") + ootb_titleblock_path = tool.Blender.get_data_dir_path( + Path("templates") / "titleblock" / (titleblock_name + ".svg") + ) titleblock_path = tool.Ifc.resolve_uri(tool.Drawing.get_default_titleblock_path(titleblock_name)) os.makedirs(sheet_dir, exist_ok=True) @@ -241,7 +243,7 @@ class SheetBuilder: title_path = os.path.join(layout_dir, "assets", "view-title.svg") os.makedirs(os.path.dirname(title_path), exist_ok=True) if not os.path.exists(title_path): - ootb_title = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", "view-title.svg") + ootb_title = tool.Blender.get_data_dir_path(Path("assets") / "view-title.svg") shutil.copy(ootb_title, title_path) title_tree = ET.parse(title_path) @@ -492,7 +494,9 @@ class SheetBuilder: return group def change_titleblock(self, sheet: ifcopenshell.entity_instance, titleblock_name: str) -> None: - ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg") + ootb_titleblock_path = tool.Blender.get_data_dir_path( + Path("templates") / "titleblocks" / (titleblock_name + ".svg") + ) titleblock_path = tool.Ifc.resolve_uri(tool.Drawing.get_default_titleblock_path(titleblock_name)) sheet_path = tool.Drawing.get_document_uri(sheet, "LAYOUT") sheet_dir = os.path.dirname(sheet_path) diff --git a/src/bonsai/bonsai/bim/module/drawing/svgwriter.py b/src/bonsai/bonsai/bim/module/drawing/svgwriter.py index 9322649ac2..e6dd66150a 100644 --- a/src/bonsai/bonsai/bim/module/drawing/svgwriter.py +++ b/src/bonsai/bonsai/bim/module/drawing/svgwriter.py @@ -38,6 +38,7 @@ from bonsai.bim.module.drawing.data import DecoratorData from math import pi, ceil, atan, degrees, acos from mathutils import geometry, Vector from typing import Optional, Self +from pathlib import Path class External(svgwrite.container.Group): @@ -98,7 +99,7 @@ class SvgWriter: os.makedirs(os.path.dirname(resource_path), exist_ok=True) if not os.path.exists(resource_path): resource_basename = os.path.basename(resource_path) - ootb_resource = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", resource_basename) + ootb_resource = tool.Blender.get_data_dir_path(Path("assets") / resource_basename) print( f"WARNING. Couldn't find {resource} for the drawing by the path: {resource_path}. Default BBIM resource will be copied from {ootb_resource}" ) diff --git a/src/bonsai/bonsai/bim/module/patch/operator.py b/src/bonsai/bonsai/bim/module/patch/operator.py index 0a1f4049ca..6438b57246 100644 --- a/src/bonsai/bonsai/bim/module/patch/operator.py +++ b/src/bonsai/bonsai/bim/module/patch/operator.py @@ -93,7 +93,7 @@ class ExecuteIfcPatch(bpy.types.Operator): args = ifcpatch.ArgumentsDict( recipe=props.ifc_patch_recipes, arguments=arguments, - log=os.path.join(context.scene.BIMProperties.data_dir, "process.log"), + log=tool.Blender.get_data_dir_path("process.log").__str__(), ) if props.should_load_from_memory and tool.Ifc.get(): diff --git a/src/bonsai/bonsai/bim/module/project/data.py b/src/bonsai/bonsai/bim/module/project/data.py index 97f82fea76..24284d16db 100644 --- a/src/bonsai/bonsai/bim/module/project/data.py +++ b/src/bonsai/bonsai/bim/module/project/data.py @@ -69,11 +69,8 @@ class ProjectData: if not ifc_file: return [] current_schema = tool.Ifc.get().schema_identifier - files = (Path(bpy.context.scene.BIMProperties.data_dir) / "libraries").iterdir() results = [("0", "Custom Library", "")] - for f in files: - if not f.suffix.lower().startswith(".ifc"): - continue + for f in tool.Blender.get_data_dir_paths("libraries", "*.ifc*"): if cls.get_file_schema(f) != current_schema: continue results.append((f.name, f.stem, "Library")) @@ -85,10 +82,7 @@ class ProjectData: for export_schema in cls.data["export_schema"]: template_files[export_schema[0]].append(("0", "Blank Project", "")) - files = (Path(bpy.context.scene.BIMProperties.data_dir) / "templates" / "projects").iterdir() - for f in files: - if not f.suffix.lower().startswith(".ifc"): - continue + for f in tool.Blender.get_data_dir_paths(Path("templates") / "projects", "*.ifc*"): current_schema = cls.get_file_schema(f) if current_schema not in template_files: continue diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index a222dbdfa9..85d94a4941 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -856,8 +856,8 @@ class LoadProjectElements(bpy.types.Operator): bonsai.bim.schema.reload(self.file.schema_identifier) start = time.time() logger = logging.getLogger("ImportIFC") - path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log") - if not os.access(context.scene.BIMProperties.data_dir, os.W_OK): + path_log = tool.Blender.get_data_dir_path("process.log") + if not os.access(path_log.parent, os.W_OK): path_log = os.path.join(tempfile.mkdtemp(), "process.log") logging.basicConfig( filename=path_log, @@ -1374,8 +1374,8 @@ class ExportIFC(bpy.types.Operator): def _execute(self, context): start = time.time() logger = logging.getLogger("ExportIFC") - path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log") - if not os.access(context.scene.BIMProperties.data_dir, os.W_OK): + path_log = tool.Blender.get_data_dir_path("process.log") + if not os.access(path_log.parent, os.W_OK): path_log = os.path.join(tempfile.mkdtemp(), "process.log") logging.basicConfig( filename=path_log, diff --git a/src/bonsai/bonsai/bim/module/project/prop.py b/src/bonsai/bonsai/bim/module/project/prop.py index 2f2cd5a9b2..13113e26cd 100644 --- a/src/bonsai/bonsai/bim/module/project/prop.py +++ b/src/bonsai/bonsai/bim/module/project/prop.py @@ -20,6 +20,7 @@ import os import bpy import ifcopenshell.util.element import ifcopenshell.util.placement +import bonsai.tool as tool from bonsai.bim.module.project.data import ProjectData from bonsai.bim.ifc import IfcStore from bonsai.bim.prop import StrProperty, ObjProperty @@ -60,9 +61,8 @@ def get_library_file(self: "BIMProjectProperties", context: bpy.types.Context) - def update_library_file(self: "BIMProjectProperties", context: bpy.types.Context) -> None: if self.library_file != "0": - bpy.ops.bim.select_library_file( - filepath=os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries", self.library_file) - ) + filepath = next(p for p in tool.Blender.get_data_dir_paths("libraries", "*.ifc") if p.name == self.library_file) + bpy.ops.bim.select_library_file(filepath=filepath) def update_filter_mode(self: "BIMProjectProperties", context: bpy.types.Context) -> None: diff --git a/src/bonsai/bonsai/bim/module/pset_template/data.py b/src/bonsai/bonsai/bim/module/pset_template/data.py index 37637ada0d..5309be7ea8 100644 --- a/src/bonsai/bonsai/bim/module/pset_template/data.py +++ b/src/bonsai/bonsai/bim/module/pset_template/data.py @@ -109,19 +109,15 @@ class PsetTemplatesData: return [(i, i, "") for i in enum_items] @classmethod - def pset_template_files(cls): - results = [] - pset_dir = os.path.join(bpy.context.scene.BIMProperties.data_dir, "pset") - files = os.listdir(pset_dir) - for f in files: - results.append( - (os.path.join(pset_dir, f), os.path.splitext(os.path.basename(f))[0], "Global Pset Template") - ) + def pset_template_files(cls) -> list[tuple[str, str, str]]: + results: list[tuple[str, str, str]] = [] + for f in tool.Blender.get_data_dir_paths("pset", "*.ifc"): + results.append((f.__str__(), f.stem, "Global Pset Template")) pset_dir = tool.Ifc.resolve_uri(bpy.context.scene.BIMProperties.pset_dir) if os.path.isdir(pset_dir): for path in pathlib.Path(pset_dir).glob("*.ifc"): - results.append((str(path), os.path.splitext(os.path.basename(str(path)))[0], "Project Pset Template")) + results.append((str(path), path.stem, "Project Pset Template")) return sorted(results, key=lambda x: x[1]) diff --git a/src/bonsai/bonsai/bim/module/pset_template/operator.py b/src/bonsai/bonsai/bim/module/pset_template/operator.py index 0e735fc136..fb48c28cea 100644 --- a/src/bonsai/bonsai/bim/module/pset_template/operator.py +++ b/src/bonsai/bonsai/bim/module/pset_template/operator.py @@ -42,7 +42,9 @@ class AddPsetTemplateFile(bpy.types.Operator): def execute(self, context): template = ifcopenshell.file() - filepath = os.path.join(context.scene.BIMProperties.data_dir, "pset", self.new_template_filename + ".ifc") + filepath = next( + p for p in tool.Blender.get_data_dir_paths("pset", "*.ifc") if p.stem == self.new_template_filename + ).__str__() pset_template = ifcopenshell.api.run("pset_template.add_pset_template", template) ifcopenshell.api.run("pset_template.add_prop_template", template, pset_template=pset_template) diff --git a/src/bonsai/bonsai/bim/operator.py b/src/bonsai/bonsai/bim/operator.py index db96a96e18..12b3c29ed8 100644 --- a/src/bonsai/bonsai/bim/operator.py +++ b/src/bonsai/bonsai/bim/operator.py @@ -812,8 +812,8 @@ class ReloadIfcFile(bpy.types.Operator, tool.Ifc.Operator): start = time.time() logger = logging.getLogger("ImportIFC") - path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log") - if not os.access(context.scene.BIMProperties.data_dir, os.W_OK): + path_log = tool.Blender.get_data_dir_path("process.log") + if not os.access(path_log.parent, os.W_OK): path_log = os.path.join(tempfile.mkdtemp(), "process.log") logging.basicConfig( filename=path_log, diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py index f0fbe146d5..7a95de8cc9 100644 --- a/src/bonsai/bonsai/bim/prop.py +++ b/src/bonsai/bonsai/bim/prop.py @@ -439,7 +439,9 @@ class BIMProperties(PropertyGroup): default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory", update=update_schema_dir ) data_dir: StringProperty( - default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory", update=update_data_dir + default=(platformdirs.user_data_path("bonsai", roaming=True, ensure_exists=True) / "data").__str__(), + name="Data Directory", + update=update_data_dir, ) cache_dir: StringProperty( default=platformdirs.user_cache_dir("bonsai"), name="Cache Directory", update=update_cache_dir diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 0035074c5f..502089d0c5 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -33,7 +33,7 @@ import importlib from mathutils import Vector from pathlib import Path from bonsai.bim.ifc import IFC_CONNECTED_TYPE -from typing import Any, Optional, Union, Literal, Iterable, Callable, TypeVar +from typing import Any, Optional, Union, Literal, Iterable, Callable, TypeVar, Generator from typing_extensions import assert_never @@ -1470,3 +1470,31 @@ class Blender(bonsai.core.tool.Blender): return "dm" if rgb_sum > threshold else "lm" except Exception: return "dm" # Default to dark mode if an error occurs + + @classmethod + def get_default_data_dir(cls) -> Path: + return Path(__file__).parent.parent / "bim" / "data" + + @classmethod + def get_custom_data_dir(cls) -> Path: + return Path(bpy.context.scene.BIMProperties.data_dir) + + @classmethod + def get_data_dir_path(cls, relative_path: Union[str, Path]) -> Path: + custom_path = cls.get_custom_data_dir() / relative_path + if custom_path.exists(): + return custom_path + return cls.get_default_data_dir() / relative_path + + @classmethod + def get_data_dir_paths(cls, relative_dir_path: Union[str, Path], glob_pattern: str) -> Generator[Path, None, None]: + custom_path = cls.get_custom_data_dir() / relative_dir_path + if custom_path.is_dir(): + for filepath in custom_path.glob(glob_pattern): + yield filepath + + default_data_dir = cls.get_default_data_dir() + if default_data_dir == custom_path: + return + for filepath in (default_data_dir / relative_dir_path).glob(glob_pattern): + yield filepath diff --git a/src/bonsai/bonsai/tool/cost.py b/src/bonsai/bonsai/tool/cost.py index 5bda3d7621..3e9f11e17f 100644 --- a/src/bonsai/bonsai/tool/cost.py +++ b/src/bonsai/bonsai/tool/cost.py @@ -9,6 +9,7 @@ import ifcopenshell.util.cost import ifcopenshell.util.unit import bonsai.bim.helper import json +from pathlib import Path from typing import Optional, Any, Generator, Union, Literal @@ -111,7 +112,7 @@ class Cost(bonsai.core.tool.Cost): device = aud.Device() # chaching.mp3 is by Lucish_ CC-BY-3.0 https://freesound.org/people/Lucish_/sounds/554841/ - sound = aud.Sound(os.path.join(bpy.context.scene.BIMProperties.data_dir, "chaching.mp3")) + sound = aud.Sound(tool.Blender.get_data_dir_path(filename="chaching.mp3").__str__()) handle = device.play(sound) sound_buffered = aud.Sound.buffer(sound) handle_buffered = device.play(sound_buffered) @@ -627,7 +628,7 @@ class Cost(bonsai.core.tool.Cost): if filepath: path = filepath else: - path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", "cost_schedules") + path = tool.Blender.get_data_dir_path(Path("build") / "cost_schedules").__str__() if not os.path.exists(path): os.makedirs(path) diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index d15a8cca49..aba09089cc 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -281,7 +281,6 @@ class Drawing(bonsai.core.tool.Drawing): import bonsai.bim.module.drawing.sheeter as sheeter sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir uri = cls.get_document_uri(document, "LAYOUT") sheet_builder.create(uri, titleblock) return uri @@ -291,7 +290,6 @@ class Drawing(bonsai.core.tool.Drawing): import bonsai.bim.module.drawing.sheeter as sheeter sheet_builder = sheeter.SheetBuilder() - sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir drawing_references = {} drawing_names = [] for reference in cls.get_document_references(sheet): @@ -1180,8 +1178,8 @@ class Drawing(bonsai.core.tool.Drawing): os.makedirs(os.path.dirname(resource_path), exist_ok=True) if not os.path.exists(resource_path): resource_basename = os.path.basename(resource_path) - ootb_resource = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", resource_basename) - if os.path.exists(ootb_resource): + ootb_resource = tool.Blender.get_data_dir_path(Path("assets") / resource_basename) + if ootb_resource.is_file(): shutil.copy(ootb_resource, resource_path) @classmethod diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py index 3bdfcab417..ca4350def0 100644 --- a/src/bonsai/bonsai/tool/project.py +++ b/src/bonsai/bonsai/tool/project.py @@ -40,8 +40,8 @@ class Project(bonsai.core.tool.Project): @classmethod def append_all_types_from_template(cls, template: str) -> None: # TODO refactor - filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "templates", "projects", template) - bpy.ops.bim.select_library_file(filepath=filepath) + filepath = tool.Blender.get_data_dir_path(Path("templates") / "projects" / template) + bpy.ops.bim.select_library_file(filepath=filepath.__str__()) if IfcStore.library_file.schema != tool.Ifc.get().schema: return for element in IfcStore.library_file.by_type("IfcTypeProduct"): diff --git a/src/bonsai/bonsai/tool/web.py b/src/bonsai/bonsai/tool/web.py index c3c9270f3d..03fcd98d2b 100644 --- a/src/bonsai/bonsai/tool/web.py +++ b/src/bonsai/bonsai/tool/web.py @@ -112,8 +112,8 @@ class Web(bonsai.core.tool.Web): global ws_process - webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui") - ws_path = os.path.join(webui_path, "sioserver.py") + webui_path = tool.Blender.get_data_dir_path("webui") + ws_path = (webui_path / "sioserver.py").__str__() py_version = sys.version_info @@ -136,7 +136,7 @@ class Web(bonsai.core.tool.Web): ws_process = subprocess.Popen( [sys.executable, ws_path, "--p", str(port), "--host", "127.0.0.1"], - cwd=webui_path, + cwd=webui_path.__str__(), env=env, ) @@ -208,8 +208,7 @@ class Web(bonsai.core.tool.Web): cls.disconnect_websocket_server() # sleep(0.5) - webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui") - pid_file = os.path.join(webui_path, "running_pid.json") + pid_file = tool.Blender.get_data_dir_path("webui") / "running_pid.json" with open(pid_file, "r") as f: pids = json.load(f) @@ -246,8 +245,7 @@ class Web(bonsai.core.tool.Web): while True: if time.time() - start > max_time: return False - webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui") - pid_file = os.path.join(webui_path, "running_pid.json") + pid_file = tool.Blender.get_data_dir_path("webui") / "running_pid.json" try: with open(pid_file, "r") as f: data = json.load(f)