Utility_Apps/Blender/simple scripts/Bonsai Preferences Script/bonsai_prefs.py
Ryan Schultz 348bd6b051 Add generic Bonsai preferences startup script
Adds bonsai_prefs.py — a Blender startup script that applies personal
Bonsai addon settings on every launch via a load_post handler, keeping
machine-specific config out of the IfcOpenShell repo.

All user settings are in a single CONFIGURE block at the top of the file.
Handles Blender extension-style addon keys (bl_ext.user_default.bonsai),
ghost addon entries with None preferences, and the register() requirement
introduced in Blender 4.2+.

Generated with the assistance of an AI coding tool.
2026-03-03 16:49:57 -06:00

125 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Bonsai personal preferences startup script.
Runs automatically at Blender startup via a load_post handler (after addons load).
Lives in Blender's user startup scripts folder — NOT in the IfcOpenShell repo.
DEBUG: Open Blender's system console (Window > Toggle System Console on Windows)
to see output from this script.
"""
import os
import bpy
from bpy.app.handlers import persistent
# ---------------------------------------------------------------------------
# CONFIGURE YOUR SETTINGS HERE
# Set any value to None to leave that preference unchanged.
# ---------------------------------------------------------------------------
# Root for shared assets/psets, relative to the open .blend file.
# e.g. os.path.join("..", "..", "my_assets") or an absolute path like "C:/Assets"
ASSETS_ROOT = os.path.join("..", "..", "..", "..", "OD_Submodules")
# Drawing asset paths — set to None to skip individual entries.
STYLESHEET_PATH = os.path.join(ASSETS_ROOT, "assets", "default.css")
SCHEDULES_STYLESHEET = os.path.join(ASSETS_ROOT, "assets", "schedule.css")
MARKERS_PATH = os.path.join(ASSETS_ROOT, "assets", "markers.svg")
SYMBOLS_PATH = os.path.join(ASSETS_ROOT, "assets", "symbols.svg")
PATTERNS_PATH = os.path.join(ASSETS_ROOT, "assets", "patterns.svg")
SHADING_STYLES_PATH = os.path.join(ASSETS_ROOT, "assets", "shading_styles.json")
# Pset directory (relative to .blend or absolute). None = leave unchanged.
PSET_DIR = os.path.join(ASSETS_ROOT, "psets") + os.path.sep
# Drawing font filename. None = leave unchanged.
DRAWING_FONT = "CENTURY_GOTHIC.TTF"
# Font scale factor. None = leave unchanged.
MAGIC_FONT_SCALE = 0.003
# Classes shown as wireframe on import. None = leave unchanged.
CLASSES_TO_WIREFRAME = "IfcVirtualElement, IfcSpace"
# Layout SVG command (Inkscape or other SVG editor). None = leave unchanged.
# Format: '[["<path/to/app>", "path"]]' where "path" is replaced with the SVG file.
LAYOUT_SVG_COMMAND = '[["C:/Program Files/Inkscape/bin/inkscape.exe", "path"]]'
# Decorations overlay colour as RGBA floats (0.01.0). None = leave unchanged.
DECORATIONS_COLOUR = (1, 0, 0, 1) # red
# ---------------------------------------------------------------------------
def find_bonsai_addon_key() -> str | None:
"""Find the Bonsai addon key — differs between legacy addons and extensions.
Legacy addon: 'bonsai'
Extension: 'bl_ext.user_default.bonsai' or 'bl_ext.user_default.bonsaiPR'
"""
addons = bpy.context.preferences.addons
if "bonsai" in addons and addons["bonsai"].preferences is not None:
return "bonsai"
# Match any key containing 'bonsai', skipping None-preferences entries (e.g. bonsaiPR ghosts).
for key in addons.keys():
if "bonsai" in key.lower():
try:
if addons[key].preferences is not None:
return key
except Exception:
continue
return None
def apply_bonsai_prefs():
addon_key = find_bonsai_addon_key()
if addon_key is None:
print("[bonsai_prefs] Bonsai not found, skipping.")
return
print(f"[bonsai_prefs] Applying prefs via addon key: '{addon_key}'")
try:
prefs = bpy.context.preferences.addons[addon_key].preferences
except Exception as e:
print(f"[bonsai_prefs] Could not access preferences: {e}")
return
if hasattr(prefs, "doc"):
doc = prefs.doc
if STYLESHEET_PATH is not None: doc.stylesheet_path = STYLESHEET_PATH
if SCHEDULES_STYLESHEET is not None: doc.schedules_stylesheet_path = SCHEDULES_STYLESHEET
if MARKERS_PATH is not None: doc.markers_path = MARKERS_PATH
if SYMBOLS_PATH is not None: doc.symbols_path = SYMBOLS_PATH
if PATTERNS_PATH is not None: doc.patterns_path = PATTERNS_PATH
if SHADING_STYLES_PATH is not None: doc.shadingstyles_path = SHADING_STYLES_PATH
if DRAWING_FONT is not None: doc.drawing_font = DRAWING_FONT
if MAGIC_FONT_SCALE is not None: doc.magic_font_scale = MAGIC_FONT_SCALE
if CLASSES_TO_WIREFRAME is not None: doc.classes_to_wireframe = CLASSES_TO_WIREFRAME
else:
print("[bonsai_prefs] prefs.doc not found — listing available properties for diagnosis:")
try:
for p in prefs.bl_rna.properties:
print(f" {p.identifier}")
except Exception as e:
print(f" (could not list: {e})")
if PSET_DIR is not None and hasattr(prefs, "pset_dir"):
prefs.pset_dir = PSET_DIR
if LAYOUT_SVG_COMMAND is not None: prefs.layout_svg_command = LAYOUT_SVG_COMMAND
if DECORATIONS_COLOUR is not None: prefs.decorations_colour = DECORATIONS_COLOUR
print("[bonsai_prefs] Done.")
@persistent
def _bonsai_prefs_load_post(*args):
apply_bonsai_prefs()
def register():
if _bonsai_prefs_load_post not in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.append(_bonsai_prefs_load_post)
def unregister():
if _bonsai_prefs_load_post in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(_bonsai_prefs_load_post)