Save all properties from Preferences UI as actual preferences

Previously some of them were saved only for current .blend file, moving them to preferences will make it more consistent with usual Blender UX. If needed we'll be able to add some way to fine-grain them later.

There's also a temporary patch that's going to migrate old .blend-props to new preferences-props to make process less disruptive.
This commit is contained in:
Andrej730
2025-07-15 16:35:15 +05:00
parent 1d0868eb18
commit 52762f828c
26 changed files with 338 additions and 184 deletions
+1
View File
@@ -146,6 +146,7 @@ classes = [
ui.BIM_UL_clipping_plane, ui.BIM_UL_clipping_plane,
ui.BIM_UL_generic, ui.BIM_UL_generic,
ui.BIM_UL_topics, ui.BIM_UL_topics,
ui.DocPreferences,
ui.BIM_ADDON_preferences, ui.BIM_ADDON_preferences,
# Tabs panel # Tabs panel
ui.BIM_PT_tabs, ui.BIM_PT_tabs,
+2
View File
@@ -417,3 +417,5 @@ def load_post(scene):
scene.tool_settings.use_snap = True scene.tool_settings.use_snap = True
# Match default Bonsai snaps # Match default Bonsai snaps
scene.tool_settings.snap_elements_base = {"EDGE", "EDGE_PERPENDICULAR", "VERTEX", "EDGE_MIDPOINT", "FACE"} scene.tool_settings.snap_elements_base = {"EDGE", "EDGE_PERPENDICULAR", "VERTEX", "EDGE_MIDPOINT", "FACE"}
tool.Blender.sync_old_preferences()
+5 -5
View File
@@ -126,11 +126,11 @@ class IfcStore:
@staticmethod @staticmethod
def get_cache() -> ifcopenshell.geom.serializers.hdf5 | None: def get_cache() -> ifcopenshell.geom.serializers.hdf5 | None:
if IfcStore.cache is None and IfcStore.path: if IfcStore.cache is None and IfcStore.path:
props = tool.Blender.get_bim_props() prefs = tool.Blender.get_addon_preferences()
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest() ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
os.makedirs(props.cache_dir, exist_ok=True) os.makedirs(prefs.cache_dir, exist_ok=True)
IfcStore.cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5") IfcStore.cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
cache_path = Path(IfcStore.cache_path) cache_path = Path(IfcStore.cache_path)
cache_settings = ifcopenshell.geom.settings() cache_settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings() serializer_settings = ifcopenshell.geom.serializer_settings()
@@ -170,8 +170,8 @@ class IfcStore:
assert IfcStore.file assert IfcStore.file
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest() ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
props = tool.Blender.get_bim_props() prefs = tool.Blender.get_addon_preferences()
new_cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5") new_cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
IfcStore.cache = None IfcStore.cache = None
try: try:
shutil.move(IfcStore.cache_path, new_cache_path) shutil.move(IfcStore.cache_path, new_cache_path)
+2 -1
View File
@@ -231,7 +231,8 @@ class IfcImporter:
self.progress = 0 self.progress = 0
self.material_creator = MaterialCreator(ifc_import_settings, self) self.material_creator = MaterialCreator(ifc_import_settings, self)
classes_to_wireframe_str = tool.Drawing.get_document_props().classes_to_wireframe prefs = tool.Blender.get_addon_preferences()
classes_to_wireframe_str = prefs.doc.classes_to_wireframe
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")] self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
def profile_code(self, message: str) -> None: def profile_code(self, message: str) -> None:
+2 -2
View File
@@ -86,8 +86,8 @@ class SheetsData:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
titleblocks_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") titleblocks_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
if not titleblocks_dir: if not titleblocks_dir:
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
titleblocks_dir = props.titleblocks_dir titleblocks_dir = prefs.doc.titleblocks_dir
titleblocks_dir = tool.Ifc.resolve_uri(titleblocks_dir) titleblocks_dir = tool.Ifc.resolve_uri(titleblocks_dir)
if os.path.exists(titleblocks_dir): if os.path.exists(titleblocks_dir):
files.extend([str(f.stem) for f in Path(titleblocks_dir).glob("*.svg")]) files.extend([str(f.stem) for f in Path(titleblocks_dir).glob("*.svg")])
@@ -426,8 +426,8 @@ class BaseDecorator:
# font_size = 16 <-- this is a good default # font_size = 16 <-- this is a good default
# TODO: need to synchronize it better with svg # TODO: need to synchronize it better with svg
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
magic_font_scale = props.magic_font_scale magic_font_scale = prefs.doc.magic_font_scale
font_size_px = int(magic_font_scale * mm_to_px) * font_size_mm / 2.5 font_size_px = int(magic_font_scale * mm_to_px) * font_size_mm / 2.5
pos = pos - line_no * font_size_px * rotation_matrix[1] pos = pos - line_no * font_size_px * rotation_matrix[1]
@@ -1661,7 +1661,7 @@ class CutDecorator:
all_vertex_i_offset = 0 all_vertex_i_offset = 0
selected_vertex_i_offset = 0 selected_vertex_i_offset = 0
classes_no_cut_str = tool.Drawing.get_document_props().classes_no_cut classes_no_cut_str = self.addon_prefs.doc.classes_no_cut
classes_no_cut = [word.strip() for word in classes_no_cut_str.split(",")] classes_no_cut = [word.strip() for word in classes_no_cut_str.split(",")]
for obj in [o for o in bpy.context.visible_objects if o.type == "MESH"]: for obj in [o for o in bpy.context.visible_objects if o.type == "MESH"]:
@@ -1959,8 +1959,8 @@ class DecorationsHandler:
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"): for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"] self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"] self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
if drawing_font := props.drawing_font: if drawing_font := prefs.doc.drawing_font:
drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font) drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font)
if drawing_font_path.is_file(): if drawing_font_path.is_file():
font_id = blf.load(drawing_font_path.__str__()) font_id = blf.load(drawing_font_path.__str__())
@@ -874,6 +874,7 @@ class CreateDrawing(bpy.types.Operator):
cached_linework -= edited_guids cached_linework -= edited_guids
bim_props = tool.Blender.get_bim_props() bim_props = tool.Blender.get_bim_props()
prefs = tool.Blender.get_addon_preferences()
files = {bim_props.ifc_file: tool.Ifc.get()} files = {bim_props.ifc_file: tool.Ifc.get()}
props = tool.Project.get_project_props() props = tool.Project.get_project_props()
@@ -890,7 +891,7 @@ class CreateDrawing(bpy.types.Operator):
# Don't use draw.main() just whilst we're prototyping and experimenting # Don't use draw.main() just whilst we're prototyping and experimenting
# TODO: hash paths are never used # TODO: hash paths are never used
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest() ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
ifc_cache_path = os.path.join(bim_props.cache_dir, f"{ifc_hash}.h5") ifc_cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
self.serialiser.setFile(ifc) self.serialiser.setFile(ifc)
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc) drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
@@ -1848,8 +1849,8 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
def poll(cls, context): def poll(cls, context):
props = tool.Drawing.get_document_props() props = tool.Drawing.get_document_props()
# Won't be visible in UI anyway. # Won't be visible in UI anyway.
bim_props = tool.Blender.get_bim_props() prefs = tool.Blender.get_addon_preferences()
if not props.sheets or not bim_props.data_dir: if not props.sheets or not prefs.data_dir:
return False return False
if not tool.Drawing.get_active_drawing_item(): if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.") cls.poll_message_set("No drawing selected.")
@@ -1954,8 +1955,8 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
if not tool.Drawing.get_active_sheet_item(is_sheet=True): if not tool.Drawing.get_active_sheet_item(is_sheet=True):
cls.poll_message_set("No sheet selected.") cls.poll_message_set("No sheet selected.")
return False return False
bim_props = tool.Blender.get_bim_props() prefs = tool.Blender.get_addon_preferences()
return props.sheets and bim_props.data_dir return props.sheets and prefs.data_dir
def invoke(self, context, event): def invoke(self, context, event):
# opening all sheets on shift+click # opening all sheets on shift+click
@@ -2741,8 +2742,8 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
if not props.schedules: if not props.schedules:
cls.poll_message_set("No schedule selected.") cls.poll_message_set("No schedule selected.")
return False return False
bim_props = tool.Blender.get_bim_props() prefs = tool.Blender.get_addon_preferences()
return props.schedules and props.sheets and bim_props.data_dir return props.schedules and props.sheets and prefs.data_dir
def _execute(self, context): def _execute(self, context):
props = tool.Drawing.get_document_props() props = tool.Drawing.get_document_props()
@@ -423,39 +423,6 @@ class DocProperties(PropertyGroup):
active_sheet_index: IntProperty(name="Active Sheet Index") active_sheet_index: IntProperty(name="Active Sheet Index")
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle) drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=update_should_draw_decorations) should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=update_should_draw_decorations)
sheets_dir: StringProperty(default=os.path.join("sheets") + os.path.sep, name="Default Sheets Directory")
layouts_dir: StringProperty(default=os.path.join("layouts") + os.path.sep, name="Default Layouts Directory")
titleblocks_dir: StringProperty(
default=os.path.join("layouts", "titleblocks") + os.path.sep, name="Default Titleblocks Directory"
)
drawings_dir: StringProperty(default=os.path.join("drawings") + os.path.sep, name="Default Drawings Directory")
stylesheet_path: StringProperty(
default=os.path.join("drawings", "assets", "default.css"), name="Default Stylesheet"
)
schedules_stylesheet_path: StringProperty(
default=os.path.join("drawings", "assets", "schedule.css"), name="Default Stylesheet for Schedules"
)
markers_path: StringProperty(default=os.path.join("drawings", "assets", "markers.svg"), name="Default Markers")
symbols_path: StringProperty(default=os.path.join("drawings", "assets", "symbols.svg"), name="Default Symbols")
patterns_path: StringProperty(default=os.path.join("drawings", "assets", "patterns.svg"), name="Default Patterns")
shadingstyles_path: StringProperty(
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
)
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
drawing_font: StringProperty(default="OpenGost Type B TT.ttf", name="Drawing Font")
magic_font_scale: bpy.props.FloatProperty(default=0.004118616, name="Font Scale Factor")
imperial_precision: StringProperty(default="1/32", name="Imperial Precision")
tolerance: bpy.props.FloatProperty(default=0.00001, name="A tolerance used when selecting objects")
classes_to_wireframe: StringProperty(
default="IfcVirtualElement",
name="Classes to Wireframe",
description="Upon import, these classes will display as wireframe.\nEx: IfcVirtualelement, IfcSpace",
)
classes_no_cut: StringProperty(
default="IfcVirtualElement, IfcSpace",
name="Classes that are not cut",
description="The cut decoractor will be turned off for these classes\nEx: IfcVirtualelement, IfcSpace",
)
if TYPE_CHECKING: if TYPE_CHECKING:
should_use_underlay_cache: bool should_use_underlay_cache: bool
@@ -480,23 +447,6 @@ class DocProperties(PropertyGroup):
active_sheet_index: int active_sheet_index: int
drawing_styles: bpy.types.bpy_prop_collection_idprop[DrawingStyle] drawing_styles: bpy.types.bpy_prop_collection_idprop[DrawingStyle]
should_draw_decorations: bool should_draw_decorations: bool
sheets_dir: str
layouts_dir: str
titleblocks_dir: str
drawings_dir: str
stylesheet_path: str
schedules_stylesheet_path: str
markers_path: str
symbols_path: str
patterns_path: str
shadingstyles_path: str
shadingstyle_default: str
drawing_font: str
magic_font_scale: float
imperial_precision: str
tolerance: float
classes_to_wireframe: str
classes_no_cut: str
def get_active_drawing(self) -> Union[ifcopenshell.entity_instance, None]: def get_active_drawing(self) -> Union[ifcopenshell.entity_instance, None]:
drawing_id = self.active_drawing_id drawing_id = self.active_drawing_id
@@ -74,10 +74,11 @@ class Scheduler:
self.schedule_xlsx(infile, outfile) self.schedule_xlsx(infile, outfile)
def parse_css(self, infile: str) -> None: def parse_css(self, infile: str) -> None:
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
stylesheet_path = os.path.splitext(infile)[0] + ".css" stylesheet_path = os.path.splitext(infile)[0] + ".css"
if not os.path.exists(stylesheet_path): if not os.path.exists(stylesheet_path):
stylesheet_rel_path = props.schedules_stylesheet_path stylesheet_rel_path = prefs.doc.schedules_stylesheet_path
ifc_file_path = os.path.dirname(tool.Ifc.get_path()) ifc_file_path = os.path.dirname(tool.Ifc.get_path())
stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path
if not os.path.exists(stylesheet_path): if not os.path.exists(stylesheet_path):
@@ -293,9 +293,9 @@ class ObjectMaterialData:
if item.is_a("IfcMaterialLayer"): if item.is_a("IfcMaterialLayer"):
total_thickness = item.LayerThickness total_thickness = item.LayerThickness
unit_system = bpy.context.scene.unit_settings.system unit_system = bpy.context.scene.unit_settings.system
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
if unit_system == "IMPERIAL": if unit_system == "IMPERIAL":
precision = props.imperial_precision precision = prefs.doc.imperial_precision
else: else:
precision = None precision = None
formatted_thickness = format_distance( formatted_thickness = format_distance(
@@ -330,11 +330,12 @@ class ObjectMaterialData:
elif cls.material.is_a("IfcMaterialLayerSet"): elif cls.material.is_a("IfcMaterialLayerSet"):
layers = cls.material.MaterialLayers layers = cls.material.MaterialLayers
thickness = sum([l.LayerThickness for l in layers or []]) thickness = sum([l.LayerThickness for l in layers or []])
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
assert bpy.context.scene
unit_system = bpy.context.scene.unit_settings.system unit_system = bpy.context.scene.unit_settings.system
precision = None precision = None
if unit_system == "IMPERIAL": if unit_system == "IMPERIAL":
precision = props.imperial_precision precision = prefs.doc.imperial_precision
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True) return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
@classmethod @classmethod
@@ -191,14 +191,6 @@ class BIMModelProperties(PropertyGroup):
menu_relating_type_id: bpy.props.IntProperty() menu_relating_type_id: bpy.props.IntProperty()
icon_id: bpy.props.IntProperty() icon_id: bpy.props.IntProperty()
updating: bpy.props.BoolProperty(default=False) updating: bpy.props.BoolProperty(default=False)
occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
name="Occurrence Name Style",
)
occurrence_name_function: bpy.props.StringProperty(
name="Occurrence Name Function",
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
)
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id} getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id}
extrusion_depth: bpy.props.FloatProperty(name="Extrusion Depth", min=0.001, default=42.0, subtype="DISTANCE") extrusion_depth: bpy.props.FloatProperty(name="Extrusion Depth", min=0.001, default=42.0, subtype="DISTANCE")
cardinal_point: bpy.props.EnumProperty( cardinal_point: bpy.props.EnumProperty(
@@ -305,8 +297,6 @@ class BIMModelProperties(PropertyGroup):
menu_relating_type_id: int menu_relating_type_id: int
icon_id: int icon_id: int
updating: bool updating: bool
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
occurrence_name_function: str
extrusion_depth: float extrusion_depth: float
cardinal_point: Literal[ cardinal_point: Literal[
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19" "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"
@@ -815,8 +815,8 @@ class SelectSimilar(Operator, tool.Ifc.Operator):
def execute(self, context): def execute(self, context):
self.calculated_sum = 0 # reset if run before self.calculated_sum = 0 # reset if run before
key = "predefined_type" if self.key == "PredefinedType" else self.key key = "predefined_type" if self.key == "PredefinedType" else self.key
dprops = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
tolerance = dprops.tolerance tolerance = prefs.doc.tolerance
formatted_tolerance = f"{tolerance:.{max(0, -int(f'{tolerance:.1e}'.split('e')[-1])) if tolerance < 1 else 1}f}" formatted_tolerance = f"{tolerance:.{max(0, -int(f'{tolerance:.1e}'.split('e')[-1])) if tolerance < 1 else 1}f}"
if self.calculate_sum: if self.calculate_sum:
@@ -57,13 +57,13 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
related_objects = [bpy.data.objects[self.related_object]] related_objects = [bpy.data.objects[self.related_object]]
else: else:
related_objects = tool.Blender.get_selected_objects() related_objects = tool.Blender.get_selected_objects()
model_props = tool.Model.get_model_props() prefs = tool.Blender.get_addon_preferences()
for obj in related_objects: for obj in related_objects:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not element or not element.is_a("IfcObject"): if not element or not element.is_a("IfcObject"):
continue continue
core.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) core.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
if model_props.occurrence_name_style == "TYPE": if prefs.occurrence_name_style == "TYPE":
obj.name = tool.Model.generate_occurrence_name(relating_type, element.is_a()) obj.name = tool.Model.generate_occurrence_name(relating_type, element.is_a())
+5 -12
View File
@@ -235,19 +235,12 @@ class SelectDir(bpy.types.Operator, ImportHelper):
bl_description = "Open a file browser to choose the directory" bl_description = "Open a file browser to choose the directory"
data_path: bpy.props.StringProperty(name="Data Path") data_path: bpy.props.StringProperty(name="Data Path")
if TYPE_CHECKING:
data_path: str
def execute(self, context): def execute(self, context):
crumbs = self.data_path.split(".") data, attr = tool.Blender.resolve_data_path_to_data_attr(self.data_path)
if crumbs[0] == "preferences": setattr(data, attr, os.path.dirname(self.filepath))
crumbs.pop(0)
data = tool.Blender.get_addon_preferences()
else:
data = context
while crumbs:
crumb = crumbs.pop(0)
if crumbs:
data = getattr(data, crumb)
else:
setattr(data, crumb, os.path.dirname(self.filepath))
return {"FINISHED"} return {"FINISHED"}
def invoke(self, context, event): def invoke(self, context, event):
-26
View File
@@ -19,7 +19,6 @@
import os import os
import bpy import bpy
import json import json
import platformdirs
import ifcopenshell import ifcopenshell
import ifcopenshell.util.pset import ifcopenshell.util.pset
import ifcopenshell.util.unit import ifcopenshell.util.unit
@@ -128,20 +127,6 @@ def update_schema_dir(self: "BIMProperties", context: bpy.types.Context) -> None
bonsai.bim.schema.ifc.schema_dir = bim_props.schema_dir bonsai.bim.schema.ifc.schema_dir = bim_props.schema_dir
def update_data_dir(self: "BIMProperties", context: bpy.types.Context) -> None:
import bonsai.bim.schema
bim_props = tool.Blender.get_bim_props()
bonsai.bim.schema.ifc.data_dir = bim_props.data_dir
def update_cache_dir(self: "BIMProperties", context: bpy.types.Context) -> None:
import bonsai.bim.schema
bim_props = tool.Blender.get_bim_props()
bonsai.bim.schema.ifc.cache_dir = bim_props.cache_dir
def update_section_color(self: "BIMProperties", context: bpy.types.Context) -> None: def update_section_color(self: "BIMProperties", context: bpy.types.Context) -> None:
section_node_group = bpy.data.node_groups.get("Section Override") section_node_group = bpy.data.node_groups.get("Section Override")
if section_node_group is None: if section_node_group is None:
@@ -521,16 +506,7 @@ class BIMProperties(PropertyGroup):
schema_dir: StringProperty( schema_dir: StringProperty(
default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory", update=update_schema_dir default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory", update=update_schema_dir
) )
data_dir: StringProperty(
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
)
has_blend_warning: BoolProperty(name="Has Blend Warning", default=False) has_blend_warning: BoolProperty(name="Has Blend Warning", default=False)
pset_dir: StringProperty(default=os.path.join("psets") + os.path.sep, name="Default Psets Directory")
ifc_file: StringProperty(name="IFC File") ifc_file: StringProperty(name="IFC File")
last_transaction: StringProperty(name="Last Transaction") last_transaction: StringProperty(name="Last Transaction")
should_section_selected_objects: BoolProperty(name="Section Selected Objects", default=False) should_section_selected_objects: BoolProperty(name="Section Selected Objects", default=False)
@@ -586,8 +562,6 @@ class BIMProperties(PropertyGroup):
if TYPE_CHECKING: if TYPE_CHECKING:
is_dirty: bool is_dirty: bool
schema_dir: str schema_dir: str
data_dir: str
cache_dir: str
has_blend_warning: bool has_blend_warning: bool
pset_dir: str pset_dir: str
ifc_file: str ifc_file: str
+4
View File
@@ -24,6 +24,10 @@ import bpy_restrict_state
class IfcSchema: class IfcSchema:
data_dir: str
cache_dir: str
schema_dir: str
def __init__(self, schema_identifier="IFC4"): def __init__(self, schema_identifier="IFC4"):
if schema_identifier not in ("IFC2X3", "IFC4", "IFC4X3_ADD2"): if schema_identifier not in ("IFC2X3", "IFC4", "IFC4X3_ADD2"):
schema_identifier = "IFC4" schema_identifier = "IFC4"
+142 -16
View File
@@ -19,6 +19,7 @@
import os import os
import bpy import bpy
import platform import platform
import platformdirs
import bonsai.bim.helper import bonsai.bim.helper
from pathlib import Path from pathlib import Path
from bpy.types import Panel from bpy.types import Panel
@@ -35,7 +36,7 @@ import bonsai.bim
import bonsai.tool as tool import bonsai.tool as tool
from ifcopenshell.util.file import IfcHeaderExtractor from ifcopenshell.util.file import IfcHeaderExtractor
from bonsai.bim.prop import Attribute from bonsai.bim.prop import Attribute
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING, Literal
from natsort import natsorted from natsort import natsorted
@@ -219,6 +220,98 @@ class BIM_UL_topics(bpy.types.UIList):
layout.label(text="", translate=False) layout.label(text="", translate=False)
class DocPreferences(bpy.types.PropertyGroup):
sheets_dir: StringProperty(
default=os.path.join("sheets") + os.path.sep,
name="Default Sheets Directory",
)
layouts_dir: StringProperty(
default=os.path.join("layouts") + os.path.sep,
name="Default Layouts Directory",
)
titleblocks_dir: StringProperty(
default=os.path.join("layouts", "titleblocks") + os.path.sep,
name="Default Titleblocks Directory",
)
drawings_dir: StringProperty(
default=os.path.join("drawings") + os.path.sep,
name="Default Drawings Directory",
)
stylesheet_path: StringProperty(
default=os.path.join("drawings", "assets", "default.css"),
name="Default Stylesheet",
)
schedules_stylesheet_path: StringProperty(
default=os.path.join("drawings", "assets", "schedule.css"),
name="Default Stylesheet for Schedules",
)
markers_path: StringProperty(
default=os.path.join("drawings", "assets", "markers.svg"),
name="Default Markers",
)
symbols_path: StringProperty(
default=os.path.join("drawings", "assets", "symbols.svg"),
name="Default Symbols",
)
patterns_path: StringProperty(
default=os.path.join("drawings", "assets", "patterns.svg"),
name="Default Patterns",
)
shadingstyles_path: StringProperty(
default=os.path.join("drawings", "assets", "shading_styles.json"),
name="Default Shading Styles",
)
shadingstyle_default: StringProperty(
default="Blender Default",
name="Default Shading Style",
)
drawing_font: StringProperty(
default="OpenGost Type B TT.ttf",
name="Drawing Font",
)
magic_font_scale: bpy.props.FloatProperty(
default=0.004118616,
name="Font Scale Factor",
)
imperial_precision: StringProperty(
default="1/32",
name="Imperial Precision",
)
tolerance: bpy.props.FloatProperty(
default=0.00001,
name="A tolerance used when selecting objects",
)
classes_to_wireframe: StringProperty(
default="IfcVirtualElement",
name="Classes to Wireframe",
description="Upon import, these classes will display as wireframe.\nEx: IfcVirtualelement, IfcSpace",
)
classes_no_cut: StringProperty(
default="IfcVirtualElement, IfcSpace",
name="Classes that are not cut",
description="The cut decoractor will be turned off for these classes\nEx: IfcVirtualelement, IfcSpace",
)
if TYPE_CHECKING:
sheets_dir: str
layouts_dir: str
titleblocks_dir: str
drawings_dir: str
stylesheet_path: str
schedules_stylesheet_path: str
markers_path: str
symbols_path: str
patterns_path: str
shadingstyles_path: str
shadingstyle_default: str
drawing_font: str
magic_font_scale: float
imperial_precision: str
tolerance: float
classes_to_wireframe: str
classes_no_cut: str
class BIM_ADDON_preferences(bpy.types.AddonPreferences): class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bl_idname = tool.Blender.get_blender_addon_package_name() bl_idname = tool.Blender.get_blender_addon_package_name()
svg2pdf_command: StringProperty( svg2pdf_command: StringProperty(
@@ -347,6 +440,39 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False
) )
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False) should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
name="Occurrence Name Style",
)
occurrence_name_function: bpy.props.StringProperty(
name="Occurrence Name Function",
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
)
def update_data_dir(self, context: bpy.types.Context) -> None:
import bonsai.bim.schema
bonsai.bim.schema.ifc.data_dir = self.data_dir
def update_cache_dir(self, context: bpy.types.Context) -> None:
import bonsai.bim.schema
bonsai.bim.schema.ifc.cache_dir = self.cache_dir
data_dir: StringProperty(
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
)
pset_dir: StringProperty(
default=os.path.join("psets") + os.path.sep,
name="Default Psets Directory",
)
doc: bpy.props.PointerProperty(type=DocPreferences)
if TYPE_CHECKING: if TYPE_CHECKING:
svg2pdf_command: str svg2pdf_command: str
@@ -374,6 +500,12 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bsdd_load_test_dictionaries: bool bsdd_load_test_dictionaries: bool
should_disable_undo_on_save: bool should_disable_undo_on_save: bool
should_stream: bool should_stream: bool
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
occurrence_name_function: str
data_dir: str
cache_dir: str
pset_dir: str
doc: DocPreferences
def draw(self, context: bpy.types.Context) -> None: def draw(self, context: bpy.types.Context) -> None:
layout = self.layout layout = self.layout
@@ -414,32 +546,26 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
layout.prop(self, "spatial_elements_unselectable") layout.prop(self, "spatial_elements_unselectable")
def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
# TODO: move props to preferences. layout.prop(self, "occurrence_name_style")
props = tool.Model.get_model_props() if self.occurrence_name_style == "CUSTOM":
layout.prop(props, "occurrence_name_style") layout.prop(self, "occurrence_name_function")
if props.occurrence_name_style == "CUSTOM":
layout.prop(props, "occurrence_name_function")
def draw_directories(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: def draw_directories(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
# TODO: move props to preferences.
props = tool.Blender.get_bim_props()
row = layout.row(align=True) row = layout.row(align=True)
row.prop(props, "data_dir") row.prop(self, "data_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.data_dir" row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.data_dir"
row = layout.row(align=True) row = layout.row(align=True)
row.prop(props, "cache_dir") row.prop(self, "cache_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.cache_dir" row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.cache_dir"
row = layout.row(align=True) row = layout.row(align=True)
row.prop(self, "tmp_dir") row.prop(self, "tmp_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.tmp_dir" row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.tmp_dir"
def draw_drawing_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: def draw_drawing_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
# TODO: move props to preferences. layout.prop(self, "pset_dir")
props = tool.Blender.get_bim_props() dprops = self.doc
layout.prop(props, "pset_dir")
dprops = tool.Drawing.get_document_props()
layout.prop(dprops, "sheets_dir") layout.prop(dprops, "sheets_dir")
layout.prop(dprops, "layouts_dir") layout.prop(dprops, "layouts_dir")
layout.prop(dprops, "titleblocks_dir") layout.prop(dprops, "titleblocks_dir")
+102 -1
View File
@@ -1550,6 +1550,51 @@ class Blender(bonsai.core.tool.Blender):
return repr(bpy_prop) return repr(bpy_prop)
return repr(bpy_struct) return repr(bpy_struct)
@classmethod
def resolve_data_path_to_data_attr(cls, data_path: str) -> tuple[bpy.types.bpy_struct, str]:
"""
:param data_path: Non-full data path to attribute.
Examples:
- `preferences.prop_group.string_prop` (`preferences` would mean addon preferences)
- `scene.string_prop` (`scene` can be any member of `Context`)
:return: Resolved tuple of Blender Struct and property name.
Examples:
- `(preferences.prop_group, "string_prop)`
- `(scene, "string_prop)`
"""
# Get data to modify.
base_path, _, data_path_ = data_path.partition(".")
if base_path == "preferences":
data = tool.Blender.get_addon_preferences()
data_path = data_path_
else:
data = bpy.context
# Get property group if available.
base_path, _, attr = data_path.rpartition(".")
if base_path:
data = data.path_resolve(base_path)
return data, attr
@classmethod
@contextlib.contextmanager
def preserve_prop_value(cls, bpy_object: bpy.types.bpy_struct, prop_name: str):
if bpy_object.is_property_set(prop_name):
prop_value = getattr(bpy_object, prop_name)
else:
prop_value = ...
try:
yield
except:
raise
finally:
if prop_value is ...:
bpy_object.property_unset(prop_name)
return
setattr(bpy_object, prop_name, prop_value)
@classmethod @classmethod
def set_prop_from_path(cls, bpy_object: bpy.types.bpy_struct, prop_path: str, value: Any) -> None: def set_prop_from_path(cls, bpy_object: bpy.types.bpy_struct, prop_path: str, value: Any) -> None:
"""Set `data_block` property value using path from `path_from_id`.""" """Set `data_block` property value using path from `path_from_id`."""
@@ -1618,7 +1663,7 @@ class Blender(bonsai.core.tool.Blender):
@classmethod @classmethod
def get_user_data_dir(cls) -> Path: def get_user_data_dir(cls) -> Path:
props = cls.get_bim_props() props = cls.get_addon_preferences()
return Path(props.data_dir) return Path(props.data_dir)
@classmethod @classmethod
@@ -1865,3 +1910,59 @@ class Blender(bonsai.core.tool.Blender):
# Remove file if crash didn't happened. # Remove file if crash didn't happened.
path.unlink() path.unlink()
@classmethod
def sync_old_preferences(cls) -> None:
# Added on 25.07.15.
# TODO: deprecate later.
settings_remap = {
"scene.BIMBSDDProperties.load_preview_dictionaries": "preferences.bsdd_load_preview_dictionaries",
"scene.BIMBSDDProperties.load_inactive_dictionaries": "preferences.bsdd_load_inactive_dictionaries",
"scene.BIMBSDDProperties.load_test_dictionaries": "preferences.bsdd_load_test_dictionaries",
"scene.BIMProjectProperties.should_disable_undo_on_save": "preferences.should_disable_undo_on_save",
"scene.BIMProjectProperties.should_stream": "preferences.should_stream",
"scene.BIMModelProperties.occurrence_name_style": "preferences.occurrence_name_style",
"scene.BIMModelProperties.occurrence_name_function": "preferences.occurrence_name_function",
"scene.BIMProperties.pset_dir": "preferences.pset_dir",
"scene.BIMProperties.data_dir": "preferences.data_dir",
"scene.BIMProperties.cache_dir": "preferences.cache_dir",
"scene.DocProperties.sheets_dir": "preferences.doc.sheets_dir",
"scene.DocProperties.layouts_dir": "preferences.doc.layouts_dir",
"scene.DocProperties.titleblocks_dir": "preferences.doc.titleblocks_dir",
"scene.DocProperties.drawings_dir": "preferences.doc.drawings_dir",
"scene.DocProperties.stylesheet_path": "preferences.doc.stylesheet_path",
"scene.DocProperties.schedules_stylesheet_path": "preferences.doc.schedules_stylesheet_path",
"scene.DocProperties.markers_path": "preferences.doc.markers_path",
"scene.DocProperties.symbols_path": "preferences.doc.symbols_path",
"scene.DocProperties.patterns_path": "preferences.doc.patterns_path",
"scene.DocProperties.shadingstyles_path": "preferences.doc.shadingstyles_path",
"scene.DocProperties.shadingstyle_default": "preferences.doc.shadingstyle_default",
"scene.DocProperties.drawing_font": "preferences.doc.drawing_font",
"scene.DocProperties.magic_font_scale": "preferences.doc.magic_font_scale",
"scene.DocProperties.imperial_precision": "preferences.doc.imperial_precision",
"scene.DocProperties.tolerance": "preferences.doc.tolerance",
"scene.DocProperties.classes_to_wireframe": "preferences.doc.classes_to_wireframe",
"scene.DocProperties.classes_no_cut": "preferences.doc.classes_no_cut",
}
props_updated = False
for old_path, path in settings_remap.items():
data, attr = cls.resolve_data_path_to_data_attr(path)
# User already overridden the value.
if data.is_property_set(attr):
continue
data_old, attr_old = cls.resolve_data_path_to_data_attr(old_path)
# User was only using default value previously.
if attr_old not in data_old:
continue
old_value = data_old[attr_old]
print(f"Updating {path} based on previous value from {old_path} - '{old_value}'.")
setattr(data, attr, old_value)
props_updated = True
# Doesn't seem to save on exit if edited from Python API, so we do it manually.
assert bpy.context.preferences
if props_updated and bpy.context.preferences.use_preferences_save:
bpy.ops.wm.save_userpref()
+2 -2
View File
@@ -57,8 +57,8 @@ class Debug(bonsai.core.tool.Debug):
@classmethod @classmethod
def purge_hdf5_cache(cls) -> None: def purge_hdf5_cache(cls) -> None:
props = tool.Blender.get_bim_props() prefs = tool.Blender.get_addon_preferences()
cache_dir = props.cache_dir cache_dir = prefs.cache_dir
filelist = [f for f in os.listdir(cache_dir) if f.endswith(".h5")] filelist = [f for f in os.listdir(cache_dir) if f.endswith(".h5")]
for f in filelist: for f in filelist:
try: try:
+17 -13
View File
@@ -1314,34 +1314,37 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def get_default_layout_path(cls, identification: str, name: str) -> str: def get_default_layout_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
layouts_dir = ( layouts_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") or props.layouts_dir ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") or prefs.doc.layouts_dir
) )
return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/") return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod @classmethod
def get_default_sheet_path(cls, identification: str, name: str) -> str: def get_default_sheet_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
sheets_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") or props.sheets_dir sheets_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") or prefs.doc.sheets_dir
)
return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/") return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod @classmethod
def get_default_titleblock_path(cls, name: str) -> str: def get_default_titleblock_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
titleblocks_dir = ( titleblocks_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") or props.titleblocks_dir ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
or prefs.doc.titleblocks_dir
) )
return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/") return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@classmethod @classmethod
def get_default_drawing_path(cls, name: str) -> str: def get_default_drawing_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
drawings_dir = ( drawings_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") or props.drawings_dir ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") or prefs.doc.drawings_dir
) )
return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/") return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@@ -1353,19 +1356,20 @@ class Drawing(bonsai.core.tool.Drawing):
RESOURCE_TYPES = ("Stylesheet", "Markers", "Symbols", "Patterns", "ShadingStyles") RESOURCE_TYPES = ("Stylesheet", "Markers", "Symbols", "Patterns", "ShadingStyles")
@classmethod @classmethod
def get_default_drawing_resource_path(cls, resource: str) -> Union[str, None]: def get_default_drawing_resource_path(cls, resource: ResourceType) -> Union[str, None]:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props() doc_prefs = tool.Blender.get_addon_preferences().doc
resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr( resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr(
props, f"{resource.lower()}_path" doc_prefs, f"{resource.lower()}_path"
) )
if resource_path: if resource_path:
assert isinstance(resource_path, str)
return resource_path.replace("\\", "/") return resource_path.replace("\\", "/")
@classmethod @classmethod
def get_default_shading_style(cls) -> str: def get_default_shading_style(cls) -> str:
dprops = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
return dprops.shadingstyle_default return prefs.doc.shadingstyle_default
@classmethod @classmethod
def setup_shading_styles_path(cls, resource_path: str) -> None: def setup_shading_styles_path(cls, resource_path: str) -> None:
+8 -6
View File
@@ -49,7 +49,7 @@ from bonsai.bim import import_ifc
from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
from bonsai.bim.module.model.opening import FilledOpeningGenerator from bonsai.bim.module.model.opening import FilledOpeningGenerator
from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d
from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict, assert_never
from collections.abc import Iterable, Sequence from collections.abc import Iterable, Sequence
T = TypeVar("T") T = TypeVar("T")
@@ -275,17 +275,19 @@ class Model(bonsai.core.tool.Model):
@classmethod @classmethod
def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str) -> str: def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str) -> str:
props = cls.get_model_props() prefs = tool.Blender.get_addon_preferences()
if props.occurrence_name_style == "CLASS": if prefs.occurrence_name_style == "CLASS":
return ifc_class[3:] return ifc_class[3:]
elif props.occurrence_name_style == "TYPE": elif prefs.occurrence_name_style == "TYPE":
return element_type.Name or "Unnamed" return element_type.Name or "Unnamed"
elif props.occurrence_name_style == "CUSTOM": elif prefs.occurrence_name_style == "CUSTOM":
try: try:
# Power users gonna power # Power users gonna power
return eval(props.occurrence_name_function) or "Instance" return eval(prefs.occurrence_name_function) or "Instance"
except: except:
return "Instance" return "Instance"
else:
assert_never(prefs.occurrence_name_style)
@classmethod @classmethod
def get_extrusion(cls, representation: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: def get_extrusion(cls, representation: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
+2 -2
View File
@@ -474,8 +474,8 @@ class Polyline(bonsai.core.tool.Polyline):
else: else:
unit_scale = tool.Blender.get_unit_scale() unit_scale = tool.Blender.get_unit_scale()
if bpy.context.scene.unit_settings.system == "IMPERIAL": if bpy.context.scene.unit_settings.system == "IMPERIAL":
dprops = tool.Drawing.get_document_props() prefs = tool.Blender.get_addon_preferences()
precision = dprops.imperial_precision precision = prefs.doc.imperial_precision
if is_area: if is_area:
unit_scale = 1 unit_scale = 1
else: else:
+1 -1
View File
@@ -69,7 +69,7 @@ class Project(bonsai.core.tool.Project):
@classmethod @classmethod
def load_pset_templates(cls) -> None: def load_pset_templates(cls) -> None:
props = tool.Blender.get_bim_props() props = tool.Blender.get_addon_preferences()
pset_dir = tool.Ifc.resolve_uri(props.pset_dir) pset_dir = tool.Ifc.resolve_uri(props.pset_dir)
if os.path.isdir(pset_dir): if os.path.isdir(pset_dir):
for path in Path(pset_dir).glob("*.ifc"): for path in Path(pset_dir).glob("*.ifc"):
+1 -1
View File
@@ -133,7 +133,7 @@ class PsetTemplate(bonsai.core.tool.PsetTemplate):
for f in tool.Blender.get_data_dir_paths("pset", "*.ifc"): for f in tool.Blender.get_data_dir_paths("pset", "*.ifc"):
paths.append((f, "Global Pset Template")) paths.append((f, "Global Pset Template"))
props = tool.Blender.get_bim_props() props = tool.Blender.get_addon_preferences()
pset_dir = Path(tool.Ifc.resolve_uri(props.pset_dir)) pset_dir = Path(tool.Ifc.resolve_uri(props.pset_dir))
if pset_dir.is_dir(): if pset_dir.is_dir():
for path in Path(pset_dir).glob("*.ifc"): for path in Path(pset_dir).glob("*.ifc"):
+2 -2
View File
@@ -55,8 +55,8 @@ class TestLoadExpress(NewFile):
class TestPurgeHdf5Cache(NewFile): class TestPurgeHdf5Cache(NewFile):
def test_run(self): def test_run(self):
props = tool.Blender.get_bim_props() prefs = tool.Blender.get_addon_preferences()
cache_dir = Path(props.cache_dir) cache_dir = Path(prefs.cache_dir)
test_file = cache_dir / "test.h5" test_file = cache_dir / "test.h5"
test_file.parent.mkdir(parents=True, exist_ok=True) test_file.parent.mkdir(parents=True, exist_ok=True)
test_file.touch() test_file.touch()
+15 -12
View File
@@ -44,26 +44,29 @@ class TestGenerateOccurrenceName(NewFile):
def test_generating_based_on_class(self): def test_generating_based_on_class(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
element_type = ifc.createIfcWallType(Name="Foobar") element_type = ifc.createIfcWallType(Name="Foobar")
props = tool.Model.get_model_props() prefs = tool.Blender.get_addon_preferences()
props.occurrence_name_style = "CLASS" with tool.Blender.preserve_prop_value(prefs, "occurrence_name_style"):
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Wall" prefs.occurrence_name_style = "CLASS"
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Wall"
def test_generating_based_on_type_name(self): def test_generating_based_on_type_name(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
element_type = ifc.createIfcWallType() element_type = ifc.createIfcWallType()
props = tool.Model.get_model_props() prefs = tool.Blender.get_addon_preferences()
props.occurrence_name_style = "TYPE" with tool.Blender.preserve_prop_value(prefs, "occurrence_name_style"):
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Unnamed" prefs.occurrence_name_style = "TYPE"
element_type.Name = "Foobar" assert subject.generate_occurrence_name(element_type, "IfcWall") == "Unnamed"
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar" element_type.Name = "Foobar"
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar"
def test_generating_based_on_a_custom_function(self): def test_generating_based_on_a_custom_function(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
element_type = ifc.createIfcWallType() element_type = ifc.createIfcWallType()
props = tool.Model.get_model_props() prefs = tool.Blender.get_addon_preferences()
props.occurrence_name_style = "CUSTOM" with tool.Blender.preserve_prop_value(prefs, "occurrence_name_style"):
props.occurrence_name_function = '"Foobar"' prefs.occurrence_name_style = "CUSTOM"
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar" prefs.occurrence_name_function = '"Foobar"'
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar"
class TestGetBooleans(NewFile): class TestGetBooleans(NewFile):