Projekt-Settings-Dialog + Library Phase A + Material-Merger
- Project-Settings-Dialog (Voreinstellungen Geschoss/Schnitt + Material-Editor) ueber Zahnrad-Icon in Oberleiste; Defaults werden in schnitte.pick_schnitt + GeschossManager als Vorgabe genommen, pro-Element-Werte unangetastet - Dossier-Library Phase A (lokal, read-only): rhino/library.py + LibraryBrowser Satellite; Seed-Manifest unter ~/Library/Application Support/Dossier/library/ - Material-Merger: _get_all_materials(doc) merged builtin _MATERIAL_LIBRARY mit Projekt-Settings-Materialien (inkl. Library-Imports); Wand-Erstellung, Sub-Layer-Anlage + Elemente-Material-Dropdown ziehen jetzt aus dem Merge Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+30
-5
@@ -2038,6 +2038,29 @@ _MATERIAL_LIBRARY = {
|
||||
}
|
||||
|
||||
|
||||
def _get_all_materials(doc):
|
||||
"""Builtin _MATERIAL_LIBRARY + Projekt-Settings-Materialien gemerged.
|
||||
Returns dict[name] -> {color, hatch, scale}. Projekt-Settings (inkl.
|
||||
Library-Imports) ueberschreibt builtin bei Namensgleichheit, sodass
|
||||
der User builtin-Defaults pro Projekt anpassen kann."""
|
||||
merged = {n: dict(m) for n, m in _MATERIAL_LIBRARY.items()}
|
||||
try:
|
||||
import rhinopanel
|
||||
ps = rhinopanel.load_project_settings(doc) if doc else None
|
||||
if isinstance(ps, dict):
|
||||
for m in ps.get("materials", []):
|
||||
n = m.get("name")
|
||||
if not n: continue
|
||||
merged[n] = {
|
||||
"color": m.get("color", "#888888"),
|
||||
"hatch": m.get("hatch", "Solid"),
|
||||
"scale": float(m.get("scale", 1.0) or 1.0),
|
||||
}
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] _get_all_materials:", ex)
|
||||
return merged
|
||||
|
||||
|
||||
def _set_layer_section_hatch(doc, layer_idx, hatch_name, scale=1.0,
|
||||
rotation=0.0):
|
||||
"""Konfiguriert Rhinos native Section-Hatch-Properties am Layer.
|
||||
@@ -2073,9 +2096,10 @@ def _ensure_material_sublayer(doc, geschoss_name, material_name):
|
||||
mit Material-Farbe + Section-Hatch konfiguriert. Liefert Layer-Index.
|
||||
Bei leerem oder unbekanntem Material: Fallback auf das normale
|
||||
Wand-Volume-Layer (= Standard fuer Solid-Waende)."""
|
||||
if not material_name or material_name not in _MATERIAL_LIBRARY:
|
||||
all_mats = _get_all_materials(doc)
|
||||
if not material_name or material_name not in all_mats:
|
||||
return _ensure_layer(doc, _layer_path_volume(doc, geschoss_name))
|
||||
mat = _MATERIAL_LIBRARY[material_name]
|
||||
mat = all_mats[material_name]
|
||||
parent_path = _layer_path_volume(doc, geschoss_name)
|
||||
full_path = "{}::{}".format(parent_path, material_name)
|
||||
idx = _ensure_layer(doc, full_path)
|
||||
@@ -5127,8 +5151,9 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
mat_name = lay_def.get("material", "") if is_layered else ""
|
||||
effective_color = color
|
||||
target_layer = layer
|
||||
if mat_name and mat_name in _MATERIAL_LIBRARY:
|
||||
effective_color = _MATERIAL_LIBRARY[mat_name]["color"]
|
||||
all_mats = _get_all_materials(doc)
|
||||
if mat_name and mat_name in all_mats:
|
||||
effective_color = all_mats[mat_name]["color"]
|
||||
target_layer = _ensure_material_sublayer(doc, geschoss_name,
|
||||
mat_name)
|
||||
if target_layer < 0: target_layer = layer
|
||||
@@ -5838,7 +5863,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
"materials": [
|
||||
{"name": n, "color": m["color"],
|
||||
"hatch": m.get("hatch", ""), "scale": m.get("scale", 1.0)}
|
||||
for n, m in _MATERIAL_LIBRARY.items()],
|
||||
for n, m in _get_all_materials(doc).items()],
|
||||
"oeffStyles": list_oeff_styles(doc),
|
||||
}
|
||||
self.send("STATE", payload)
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
library.py — Dossier-Library (Phase A: lokal, read-only)
|
||||
|
||||
Library = wiederverwendbare Material-/Symbol-/Object-Templates die ein User
|
||||
oder ein Team teilt. Phase A: lokaler Ordner mit library.json + Assets.
|
||||
Spaeter (Phase B/C): Cloud-Sync via GitHub-Releases.
|
||||
|
||||
Library-Root: ~/Library/Application Support/Dossier/library/
|
||||
Struktur:
|
||||
library/
|
||||
library.json # Manifest (Schema-Version + items[])
|
||||
previews/ # PNG-Thumbnails
|
||||
assets/ # .3dm-Fragmente fuer symbol/object
|
||||
|
||||
Manifest-Item:
|
||||
{
|
||||
"id": "mat-beton-roh-v1", # global eindeutig (UUID/Slug)
|
||||
"type": "material", # material | symbol | object
|
||||
"name": "Beton roh",
|
||||
"version": 1, # Schema-Version des Items
|
||||
"tags": ["beton", "tragwerk"],
|
||||
"preview": "previews/mat-beton-roh.png",
|
||||
"data": { ... } # Typ-spezifische Felder
|
||||
}
|
||||
|
||||
Material-data: { color, hatch, scale }
|
||||
Symbol/Object-data: { files: ["assets/sym-foo.3dm"] }
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
|
||||
# Lazy Import von Rhino — library.py soll auch in einem Test-Skript ohne
|
||||
# Rhino-Kontext importierbar sein (z.B. fuer Seed-Logik).
|
||||
try:
|
||||
import Rhino # noqa: F401
|
||||
except Exception:
|
||||
Rhino = None
|
||||
|
||||
|
||||
_LIB_DIRNAME = "library"
|
||||
_MANIFEST_FN = "library.json"
|
||||
_SCHEMA_VERSION = 1
|
||||
|
||||
|
||||
def library_root():
|
||||
"""Mac-Pfad zur lokalen Library. Wird bei Bedarf angelegt."""
|
||||
base = os.path.expanduser(
|
||||
"~/Library/Application Support/Dossier")
|
||||
return os.path.join(base, _LIB_DIRNAME)
|
||||
|
||||
|
||||
def ensure_library():
|
||||
"""Legt Library-Folder + Sub-Folders + Seed-Manifest an wenn leer."""
|
||||
root = library_root()
|
||||
if not os.path.isdir(root):
|
||||
try: os.makedirs(root)
|
||||
except Exception as ex:
|
||||
print("[LIBRARY] mkdir:", ex); return root
|
||||
for sub in ("previews", "assets"):
|
||||
p = os.path.join(root, sub)
|
||||
if not os.path.isdir(p):
|
||||
try: os.makedirs(p)
|
||||
except Exception: pass
|
||||
manifest_path = os.path.join(root, _MANIFEST_FN)
|
||||
if not os.path.isfile(manifest_path):
|
||||
_write_seed_manifest(manifest_path)
|
||||
return root
|
||||
|
||||
|
||||
def _write_seed_manifest(path):
|
||||
"""Bootstrappt 4 typische Architektur-Materialien als Start."""
|
||||
seed = {
|
||||
"schemaVersion": _SCHEMA_VERSION,
|
||||
"name": "Dossier-Library (lokal)",
|
||||
"items": [
|
||||
{
|
||||
"id": "mat-beton-sichtbeton-v1",
|
||||
"type": "material", "version": 1,
|
||||
"name": "Beton — Sichtbeton",
|
||||
"tags": ["beton", "tragwerk", "roh"],
|
||||
"data": {"color": "#a8a39b", "hatch": "Solid", "scale": 1.0},
|
||||
},
|
||||
{
|
||||
"id": "mat-mauerwerk-backstein-v1",
|
||||
"type": "material", "version": 1,
|
||||
"name": "Mauerwerk — Backstein",
|
||||
"tags": ["mauerwerk", "stein"],
|
||||
"data": {"color": "#a45a3c", "hatch": "Solid", "scale": 1.0},
|
||||
},
|
||||
{
|
||||
"id": "mat-daemmung-mineralwolle-v1",
|
||||
"type": "material", "version": 1,
|
||||
"name": "Daemmung — Mineralwolle",
|
||||
"tags": ["daemmung", "weich"],
|
||||
"data": {"color": "#e8d36b", "hatch": "Solid", "scale": 1.0},
|
||||
},
|
||||
{
|
||||
"id": "mat-holz-fichte-v1",
|
||||
"type": "material", "version": 1,
|
||||
"name": "Holz — Fichte",
|
||||
"tags": ["holz", "ausbau"],
|
||||
"data": {"color": "#c8a06a", "hatch": "Solid", "scale": 1.0},
|
||||
},
|
||||
],
|
||||
}
|
||||
try:
|
||||
with open(path, "w") as f:
|
||||
json.dump(seed, f, indent=2, ensure_ascii=False)
|
||||
print("[LIBRARY] Seed geschrieben:", path)
|
||||
except Exception as ex:
|
||||
print("[LIBRARY] seed write:", ex)
|
||||
|
||||
|
||||
def load_manifest():
|
||||
"""Liest library.json — legt Library bei Bedarf an. Returns dict mit
|
||||
schemaVersion + name + items[]. Bei Fehler leeres Manifest."""
|
||||
ensure_library()
|
||||
path = os.path.join(library_root(), _MANIFEST_FN)
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
data = json.load(f)
|
||||
if not isinstance(data, dict): return _empty_manifest()
|
||||
items = data.get("items")
|
||||
if not isinstance(items, list): data["items"] = []
|
||||
else: data["items"] = [_normalize_item(x) for x in items
|
||||
if _normalize_item(x) is not None]
|
||||
return data
|
||||
except Exception as ex:
|
||||
print("[LIBRARY] load_manifest:", ex)
|
||||
return _empty_manifest()
|
||||
|
||||
|
||||
def _empty_manifest():
|
||||
return {"schemaVersion": _SCHEMA_VERSION,
|
||||
"name": "Dossier-Library", "items": []}
|
||||
|
||||
|
||||
def _normalize_item(it):
|
||||
if not isinstance(it, dict): return None
|
||||
if not it.get("id") or not it.get("type"): return None
|
||||
out = {
|
||||
"id": str(it["id"]),
|
||||
"type": str(it["type"]),
|
||||
"name": str(it.get("name") or "Unbenannt"),
|
||||
"version": int(it.get("version") or 1),
|
||||
"tags": list(it.get("tags") or []),
|
||||
"preview": it.get("preview"),
|
||||
"data": it.get("data") or {},
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
def find_item(item_id):
|
||||
"""Sucht ein Item per id im Manifest. Returns None wenn nicht da."""
|
||||
if not item_id: return None
|
||||
for it in load_manifest().get("items", []):
|
||||
if it.get("id") == item_id: return it
|
||||
return None
|
||||
|
||||
|
||||
# --- Import-Logik -----------------------------------------------------------
|
||||
|
||||
def import_material(doc, item):
|
||||
"""Importiert ein Material-Item in die Projekt-Settings des Doc. Dedupe
|
||||
per libraryId — wenn schon importiert, kein Doppel-Eintrag.
|
||||
Returns (ok, message)."""
|
||||
if doc is None: return False, "Kein aktives Dokument"
|
||||
if not isinstance(item, dict) or item.get("type") != "material":
|
||||
return False, "Item ist kein Material"
|
||||
data = item.get("data") or {}
|
||||
new_mat = {
|
||||
"name": item.get("name") or "Unbenannt",
|
||||
"color": data.get("color", "#888888"),
|
||||
"hatch": data.get("hatch", "Solid"),
|
||||
"scale": float(data.get("scale", 1.0) or 1.0),
|
||||
"source": "library",
|
||||
"libraryId": item.get("id"),
|
||||
}
|
||||
# Lazy-Import um Zyklen zu vermeiden
|
||||
import rhinopanel
|
||||
settings = rhinopanel.load_project_settings(doc)
|
||||
mats = list(settings.get("materials", []))
|
||||
for m in mats:
|
||||
if m.get("libraryId") == new_mat["libraryId"]:
|
||||
return False, "Material bereits importiert"
|
||||
mats.append(new_mat)
|
||||
settings["materials"] = mats
|
||||
rhinopanel.save_project_settings(doc, settings)
|
||||
return True, "Material importiert"
|
||||
|
||||
|
||||
def import_item(doc, item_id):
|
||||
"""Type-dispatching Import. Phase A: nur material. symbol/object kommen
|
||||
spaeter via File3dm.Read + InstanceDefinition."""
|
||||
item = find_item(item_id)
|
||||
if item is None: return False, "Item nicht gefunden: " + str(item_id)
|
||||
t = item.get("type")
|
||||
if t == "material":
|
||||
return import_material(doc, item)
|
||||
# Phase A: symbol/object noch nicht
|
||||
return False, "Typ '{}' wird erst in Phase A.2 unterstuetzt".format(t)
|
||||
@@ -96,6 +96,7 @@ def _broadcast_state(doc=None, hatch_patterns=None):
|
||||
"zeichnungsebenen": json.loads(z_raw) if z_raw else None,
|
||||
"ebenen": json.loads(e_raw) if e_raw else None,
|
||||
"projectZeroMum": zero_mum,
|
||||
"projectSettings": load_project_settings(doc),
|
||||
"hatchPatterns": hatch_patterns if hatch_patterns is not None
|
||||
else _hatch_pattern_names(doc),
|
||||
"layerCombinations": list_layer_preset_names(doc),
|
||||
@@ -120,6 +121,83 @@ def _broadcast_state(doc=None, hatch_patterns=None):
|
||||
_PRESETS_KEY = "dossier_layer_presets"
|
||||
_ACTIVE_COMB_KEY = "dossier_layer_active_comb"
|
||||
|
||||
# Projekt-Einstellungen: zentrale Voreinstellungen die beim Erstellen
|
||||
# neuer Geschosse / Schnitte / etc. als Default genommen werden. Pro-
|
||||
# Element-Werte ueberschreiben das natuerlich — das hier ist nur die
|
||||
# Voreinstellung. Persistiert pro Doc.
|
||||
_PROJECT_SETTINGS_KEY = "dossier_project_settings"
|
||||
|
||||
_PROJECT_SETTINGS_DEFAULTS = {
|
||||
"defaults": {
|
||||
"geschossHoehe": 3.0,
|
||||
"schnitthoehe": 1.0,
|
||||
"schnittDepthBack": 8.0,
|
||||
"schnittHeightMin": -1.0,
|
||||
"schnittHeightMax": 12.0,
|
||||
},
|
||||
"materials": [], # User-erweiterte Materialien (zusaetzlich zur
|
||||
# hardcoded _MATERIAL_LIBRARY in elemente.py)
|
||||
}
|
||||
|
||||
|
||||
def _normalize_material(m):
|
||||
"""Garantiert Material-Schema: name + color + hatch + scale + source
|
||||
+ libraryId. source: 'local' | 'library' | 'builtin'. libraryId: UUID
|
||||
wenn aus Library, sonst null. Felder fehlen bei alten Eintraegen — wir
|
||||
setzen Defaults damit Frontend nicht null-checken muss."""
|
||||
if not isinstance(m, dict): return None
|
||||
return {
|
||||
"name": m.get("name") or "Unbenannt",
|
||||
"color": m.get("color") or "#888888",
|
||||
"hatch": m.get("hatch") or "Solid",
|
||||
"scale": float(m.get("scale", 1.0) or 1.0),
|
||||
"source": m.get("source") or "local",
|
||||
"libraryId": m.get("libraryId"), # None wenn nicht aus Library
|
||||
}
|
||||
|
||||
|
||||
def load_project_settings(doc):
|
||||
"""Liefert die Project-Settings als dict — mit Defaults-Merge wenn
|
||||
Felder fehlen. Garantiert dass `defaults` und `materials` immer da
|
||||
sind, und Materialien normalisiert (source + libraryId)."""
|
||||
raw = None
|
||||
try: raw = doc.Strings.GetValue(_PROJECT_SETTINGS_KEY) if doc else None
|
||||
except Exception: raw = None
|
||||
out = {
|
||||
"defaults": dict(_PROJECT_SETTINGS_DEFAULTS["defaults"]),
|
||||
"materials": list(_PROJECT_SETTINGS_DEFAULTS["materials"]),
|
||||
}
|
||||
if not raw: return out
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
if isinstance(data, dict):
|
||||
d = data.get("defaults")
|
||||
if isinstance(d, dict):
|
||||
for k, v in d.items():
|
||||
out["defaults"][k] = v
|
||||
m = data.get("materials")
|
||||
if isinstance(m, list):
|
||||
out["materials"] = [
|
||||
_normalize_material(x) for x in m
|
||||
if _normalize_material(x) is not None
|
||||
]
|
||||
except Exception as ex:
|
||||
print("[PROJECT-SETTINGS] load:", ex)
|
||||
return out
|
||||
|
||||
|
||||
def save_project_settings(doc, settings):
|
||||
"""Persistiert Settings in doc.Strings. settings: dict mit
|
||||
'defaults' + 'materials'. Caller broadcastet ggf. selbst."""
|
||||
if doc is None or not isinstance(settings, dict): return False
|
||||
try:
|
||||
doc.Strings.SetString(_PROJECT_SETTINGS_KEY,
|
||||
json.dumps(settings, ensure_ascii=False))
|
||||
return True
|
||||
except Exception as ex:
|
||||
print("[PROJECT-SETTINGS] save:", ex)
|
||||
return False
|
||||
|
||||
|
||||
def load_layer_presets(doc):
|
||||
raw = doc.Strings.GetValue(_PRESETS_KEY)
|
||||
@@ -531,9 +609,131 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
try: open_layer_combinations_window()
|
||||
except Exception as ex:
|
||||
print("[EBENEN] open layer-combinations:", ex)
|
||||
elif t == "OPEN_PROJECT_SETTINGS":
|
||||
try: self._open_project_settings()
|
||||
except Exception as ex:
|
||||
print("[EBENEN] open project-settings:", ex)
|
||||
elif t == "OPEN_LIBRARY":
|
||||
try: self._open_library()
|
||||
except Exception as ex:
|
||||
print("[EBENEN] open library:", ex)
|
||||
|
||||
# ---- Helpers ----
|
||||
|
||||
def _open_project_settings(self):
|
||||
"""Oeffnet React-Satellite mit Projekt-Voreinstellungen (Geschoss-/
|
||||
Schnitt-Defaults + Material-Library). Werte werden nur als
|
||||
Voreinstellung beim Erstellen neuer Elemente genutzt — pro-Element
|
||||
editierte Werte bleiben davon unberuehrt."""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
current = load_project_settings(doc)
|
||||
# Hardcoded Material-Library aus elemente.py mit-laden damit
|
||||
# Frontend die Defaults zeigt + User sie ggf. anpassen kann.
|
||||
try:
|
||||
import elemente
|
||||
built_in = []
|
||||
for name, m in elemente._MATERIAL_LIBRARY.items():
|
||||
built_in.append({
|
||||
"name": name,
|
||||
"color": m.get("color", "#888888"),
|
||||
"hatch": m.get("hatch", "Solid"),
|
||||
"scale": float(m.get("scale", 1.0)),
|
||||
"source": "builtin",
|
||||
"libraryId": None,
|
||||
"builtin": True, # legacy flag fuer aelteren UI-Code
|
||||
})
|
||||
except Exception:
|
||||
built_in = []
|
||||
params = {
|
||||
"defaults": current.get("defaults", {}),
|
||||
"materials": current.get("materials", []),
|
||||
"builtinMaterials": built_in,
|
||||
"hatchPatterns": _hatch_pattern_names(doc),
|
||||
}
|
||||
def on_save(updated):
|
||||
doc2 = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc2 is None: return
|
||||
new_settings = {
|
||||
"defaults": updated.get("defaults", {}),
|
||||
"materials": updated.get("materials", []),
|
||||
}
|
||||
save_project_settings(doc2, new_settings)
|
||||
_broadcast_state(doc2)
|
||||
try:
|
||||
import scriptcontext as sc
|
||||
eb = sc.sticky.get("elemente_bridge")
|
||||
if eb is not None: eb._send_state()
|
||||
except Exception as ex:
|
||||
print("[PROJECT-SETTINGS] elemente refresh:", ex)
|
||||
panel_base.open_satellite_window(
|
||||
"project_settings",
|
||||
params=params,
|
||||
title="Projekt-Einstellungen",
|
||||
size=(440, 540),
|
||||
on_save=on_save)
|
||||
|
||||
def _open_library(self):
|
||||
"""Oeffnet den Library-Browser als Satellite. Bridge bleibt offen
|
||||
damit User mehrere Items hintereinander importieren kann; nach
|
||||
jedem Import wird der Material-Status zurueckgemeldet damit das
|
||||
UI 'importiert' anzeigt."""
|
||||
import library
|
||||
bridge_holder = {"form": None}
|
||||
class _LibraryBridge(panel_base.BaseBridge):
|
||||
def __init__(self):
|
||||
panel_base.BaseBridge.__init__(self, "library")
|
||||
def _on_ready(self):
|
||||
self._send_state()
|
||||
def _send_state(self):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
manifest = library.load_manifest()
|
||||
imported_ids = set()
|
||||
if doc is not None:
|
||||
settings = load_project_settings(doc)
|
||||
for m in settings.get("materials", []):
|
||||
lid = m.get("libraryId")
|
||||
if lid: imported_ids.add(lid)
|
||||
self.send("LIBRARY_STATE", {
|
||||
"manifest": manifest,
|
||||
"importedIds": list(imported_ids),
|
||||
"libraryRoot": library.library_root(),
|
||||
})
|
||||
def handle(self, data):
|
||||
if not isinstance(data, dict): return
|
||||
t = data.get("type", "")
|
||||
p = data.get("payload") or {}
|
||||
if t == "READY":
|
||||
self._on_ready()
|
||||
elif t == "IMPORT_ITEM":
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
ok, msg = library.import_item(doc, p.get("id"))
|
||||
print("[LIBRARY] import {}: {} ({})".format(
|
||||
p.get("id"), ok, msg))
|
||||
if ok:
|
||||
_broadcast_state(doc)
|
||||
# Elemente-Panel-Dropdown muss neu laden (materials)
|
||||
try:
|
||||
import scriptcontext as sc
|
||||
eb = sc.sticky.get("elemente_bridge")
|
||||
if eb is not None: eb._send_state()
|
||||
except Exception as ex:
|
||||
print("[LIBRARY] elemente refresh:", ex)
|
||||
self._send_state()
|
||||
elif t == "RELOAD":
|
||||
self._send_state()
|
||||
elif t == "CLOSE":
|
||||
try:
|
||||
f = bridge_holder.get("form")
|
||||
if f is not None: f.Close()
|
||||
except Exception: pass
|
||||
b = _LibraryBridge()
|
||||
bridge_holder["form"] = panel_base.open_satellite_window(
|
||||
"library",
|
||||
params={},
|
||||
title="Dossier-Library",
|
||||
size=(640, 560),
|
||||
bridge=b)
|
||||
|
||||
def _open_geschoss_settings(self, geschoss):
|
||||
"""Oeffnet ein echtes Rhino-Fenster (Eto.Form mit WebView) mit dem
|
||||
GeschossSettingsDialog. Save updated den Eintrag in doc.Strings +
|
||||
|
||||
+17
-4
@@ -589,12 +589,25 @@ def pick_schnitt_interactive(doc, defaults=None):
|
||||
"""Interaktiver Pick: 2 Punkte + Dir-Pfeil + Defaults aus settings.
|
||||
Liefert die neue Schnitt-Id oder None bei Abbruch.
|
||||
|
||||
defaults: {depthBack, heightMin, heightMax, cutAtLine, namePrefix}"""
|
||||
defaults: {depthBack, heightMin, heightMax, cutAtLine, namePrefix}
|
||||
Wenn ein Feld fehlt, wird aus dossier_project_settings nachgeschaut
|
||||
(zentrale Project-Voreinstellung), sonst Hardcoded-Default."""
|
||||
defaults = defaults or {}
|
||||
# Project-Defaults als 2-stufiges Fallback (defaults > project > hardcoded)
|
||||
proj_d = {}
|
||||
try:
|
||||
import rhinopanel
|
||||
ps = rhinopanel.load_project_settings(doc) or {}
|
||||
proj_d = ps.get("defaults", {}) or {}
|
||||
except Exception: pass
|
||||
def _resolve(key, proj_key, fallback):
|
||||
if key in defaults: return defaults[key]
|
||||
if proj_key in proj_d: return proj_d[proj_key]
|
||||
return fallback
|
||||
name_prefix = defaults.get("namePrefix", "S")
|
||||
depth_back = float(defaults.get("depthBack", 8.0))
|
||||
h_min = float(defaults.get("heightMin", -1.0))
|
||||
h_max = float(defaults.get("heightMax", 12.0))
|
||||
depth_back = float(_resolve("depthBack", "schnittDepthBack", 8.0))
|
||||
h_min = float(_resolve("heightMin", "schnittHeightMin", -1.0))
|
||||
h_max = float(_resolve("heightMax", "schnittHeightMax", 12.0))
|
||||
cut_at_line = bool(defaults.get("cutAtLine", True))
|
||||
|
||||
# Pick Punkt 1
|
||||
|
||||
Reference in New Issue
Block a user