DOSSIER Multi-Phase: C#-Plugin + Yak + Wandstile + UX-Polish

- C#-Plugin "DOSSIER" mit 23 nativen Commands (dWall, dDoor, ..., dSection)
  - Native Command-Namen + Autocomplete + saubere History
  - Idle-Defer + RhinoCode-API → kein _-RunPythonScript-Echo
  - Yak-Paket via build.sh, Install in ~/Library/.../packages/8.0/
- Launcher (Tauri):
  - dossier_init Tauri-Command + Setup-Tab in Settings
  - Yak-Install + StartupCommands-XML + Window-Layout in einem Schritt
  - clean-rhino.sh fuer reproduzierbare Resets
  - check_dossier_initialized triggert Auto-Open-Setup beim ersten Start
- Wand-Architektur:
  - Chain-Logik DEAKTIVIERT → jede Wand baut eigenes Volume (individuell
    anwaehlbar, einzeln loeschbar)
  - Polyline-Wand: jedes Segment = eigene Wand
  - Smart-Split fuer wand_axis/decke/dach/raum/aussparung/traeger
  - Auto-Group axis+volume → kein ChooseOne-Dialog, Delete loescht beides
  - Stale-Mitre-Fix: Joint-Cache wird vor jedem Wand-Regen invalidiert
  - T-Junction-Tolerance auf 1mm (war 1cm, lieferte falsche T-Mitres)
- Wand-Stile:
  - Schema in dossier_project_settings.wand_styles (Material + Prio +
    Default-Dicke + Referenz, oder Layered mit Schichten)
  - dWall-Command Stil-Picker
  - ProjectSettingsDialog: Sidebar-Layout (Pill-Selection) +
    Wandstile-Tab mit Liste/Editor
  - _wand_chain_compat benutzt style_id
  - Prio-Dominanz: hoehere Prio gewinnt Eckverbindung, niedrigere wird
    T-mitered (siehe _resolve_corner_miter)
- Cmd+G fuer Group (Geschoss-Up auf Alias 'gu')
- Welcome + Cheatsheet borderless mit X/Back-Buttons
- BeginCommand-Hook fuer Gestaltung-Panel-Auto-Open
- panel_base: Python.NET-Enum-Fix fuer Material-Render
This commit is contained in:
2026-05-30 12:46:53 +02:00
parent 7930705d01
commit 18d6d98e07
54 changed files with 5575 additions and 398 deletions
+98 -6
View File
@@ -309,6 +309,47 @@ _PROJECT_SETTINGS_DEFAULTS = {
"unit": "meters", # "meters" | "millimeters" | "centimeters"
},
"materials": [],
# Wand-Stile: Wand-Typ-Templates mit Prio fuer Joint-Dominanz.
#
# SOLID-Style (layered=False):
# material = Material-Identitaet (driver fuer Section-Hatch)
# dicke = DEFAULT bei Erstellung — User kann pro Wand ueberschreiben
# referenz = DEFAULT
#
# LAYERED-Style (layered=True):
# layers = fixe Schicht-Komposition mit Material+Dicke pro Layer
# dicke = ignoriert (kommt aus Summe der Layers)
#
# PRIO (1-999, 999=dominant): zwei Wand-Stile koennen das gleiche Material
# haben aber verschiedene Prios (z.B. Beton tragend prio=800,
# Beton innen prio=400). Bei Joints zwischen verschiedenen Stilen gewinnt
# der mit hoeherer Prio die Ecke (Phase 3, noch nicht implementiert).
#
# SECTION-MERGE (Phase 2, noch nicht implementiert): Hatches mergen visuell
# an Joints zwischen Waenden mit GLEICHEM Material — egal ob gleicher Stil.
# So sind „Beton tragend" und „Beton innen" im Schnitt verbunden.
"wand_styles": [
{"id": "style_beton_tragend", "name": "Beton tragend",
"prio": 800, "dicke": 0.25, "referenz": "mid",
"layered": False, "material": "Stahlbeton", "layers": []},
{"id": "style_beton_innen", "name": "Beton innen",
"prio": 400, "dicke": 0.15, "referenz": "mid",
"layered": False, "material": "Stahlbeton", "layers": []},
{"id": "style_gips", "name": "Gipswand",
"prio": 200, "dicke": 0.10, "referenz": "mid",
"layered": False, "material": "Putz", "layers": []},
{"id": "style_mauerwerk", "name": "Mauerwerk",
"prio": 300, "dicke": 0.12, "referenz": "mid",
"layered": False, "material": "Mauerwerk", "layers": []},
{"id": "style_aussen30", "name": "Aussenwand 30 cm gedaemmt",
"prio": 900, "dicke": 0.30, "referenz": "left",
"layered": True, "material": "",
"layers": [
{"material": "Stahlbeton", "dicke": 0.18},
{"material": "Daemmung", "dicke": 0.10},
{"material": "Putz", "dicke": 0.02},
]},
],
"project": {
"name": "",
"number": "",
@@ -379,6 +420,48 @@ def _normalize_project_meta(p):
}
def _normalize_wand_style(s):
"""Garantiert Wand-Style-Schema. Stil = Bundle aus Geometrie + Prio.
Felder:
- id (str, kebab-case empfohlen), name (str)
- prio (int 1-999): bei Joints zwischen verschiedenen Stilen wins der hoehere
- dicke (float, Meter)
- referenz ('mid'|'left'|'right')
- layered (bool): wenn True, layers definieren die Schichten
- material (str): bei layered=False der Material-Name
- layers (list of {material, dicke}): bei layered=True"""
if not isinstance(s, dict): return None
sid = str(s.get("id") or "").strip()
if not sid: return None
try: prio = int(s.get("prio", 500))
except Exception: prio = 500
prio = max(1, min(999, prio))
try: dicke = float(s.get("dicke", 0.25))
except Exception: dicke = 0.25
ref = str(s.get("referenz") or "mid")
if ref not in ("mid", "left", "right"): ref = "mid"
layered = bool(s.get("layered"))
layers = []
if layered and isinstance(s.get("layers"), list):
for ly in s["layers"]:
if not isinstance(ly, dict): continue
try: ld = float(ly.get("dicke", 0))
except Exception: ld = 0.0
if ld <= 0: continue
layers.append({"material": str(ly.get("material") or ""),
"dicke": ld})
return {
"id": sid,
"name": str(s.get("name") or sid),
"prio": prio,
"dicke": dicke,
"referenz": ref,
"layered": layered,
"material": str(s.get("material") or ""),
"layers": layers,
}
def _normalize_material(m):
"""Garantiert Material-Schema. Material ist REIN 3D — Section-Hatch
(2D-Schnitt) wird via Ebenen-Settings am Layer konfiguriert.
@@ -440,9 +523,10 @@ def load_project_settings(doc):
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"]),
"project": dict(_PROJECT_SETTINGS_DEFAULTS["project"]),
"defaults": dict(_PROJECT_SETTINGS_DEFAULTS["defaults"]),
"materials": list(_PROJECT_SETTINGS_DEFAULTS["materials"]),
"wand_styles": [dict(s) for s in _PROJECT_SETTINGS_DEFAULTS["wand_styles"]],
"project": dict(_PROJECT_SETTINGS_DEFAULTS["project"]),
}
if raw:
try:
@@ -458,6 +542,12 @@ def load_project_settings(doc):
_normalize_material(x) for x in m
if _normalize_material(x) is not None
]
ws = data.get("wand_styles")
if isinstance(ws, list):
out["wand_styles"] = [
_normalize_wand_style(s) for s in ws
if _normalize_wand_style(s) is not None
]
pr = data.get("project")
if isinstance(pr, dict):
out["project"] = _normalize_project_meta(pr)
@@ -976,6 +1066,7 @@ class EbenenBridge(panel_base.BaseBridge):
"defaults": current.get("defaults", {}),
"project": current.get("project", {}),
"materials": current.get("materials", []),
"wandStyles": current.get("wand_styles", []),
"builtinMaterials": built_in,
"hatchPatterns": _hatch_pattern_names(doc),
"hatchPatternsFull": _list_hatch_patterns_full(doc),
@@ -990,9 +1081,10 @@ class EbenenBridge(panel_base.BaseBridge):
doc2 = Rhino.RhinoDoc.ActiveDoc
if doc2 is None: return
new_settings = {
"defaults": updated.get("defaults", {}),
"materials": updated.get("materials", []),
"project": updated.get("project", {}),
"defaults": updated.get("defaults", {}),
"materials": updated.get("materials", []),
"wand_styles": updated.get("wandStyles", []),
"project": updated.get("project", {}),
}
save_project_settings(doc2, new_settings)
_broadcast_state(doc2)