Architektur
Architektur
DOSSIER ist als Plugin-Verbund aufgebaut: jedes Feature lebt in einem eigenen Modul, alle teilen sich ein gemeinsames Bridge-Pattern für die React-↔-Python-Kommunikation.
Module-Map
| Modul | LOC | Rolle |
|---|---|---|
panel_base.py | 697 | Fundament: BaseBridge, WebView-IO, Panel-Registration, Icons |
rhinopanel.py | 798 | EBENEN — Zeichnungsebenen, Layer-Hierarchie, Presets |
elemente.py | 7'244 | ELEMENTE — Wände, Decken, Öffnungen, Treppen, Tragwerk, Räume |
gestaltung.py | 1'635 | GESTALTUNG — Selektions-Attribute (Farbe, Lineweight, Hatch) |
oberleiste.py | 981 | OBERLEISTE — Top-Bar, Display, Massstab, Snaps, Settings |
massstab.py | 1'096 | MASSSTAB — Viewport 1:N, Auto-DPI, PlotWeight |
overrides.py | 797 | Engine — regelbasierte Overrides (Bedingung → Aktion) |
overrides_panel.py | 226 | UI für Overrides-Engine |
ausschnitte.py | 708 | AUSSCHNITTE — Viewport-Snapshots (Kamera + Display + Layer) |
dimensionen.py | 613 | DIMENSIONEN — Bemassung (Wand-Dicken, Geschoss-Höhen, …) |
layouts.py | 749 | LAYOUTS — Plan-Editor, Titelblock, PDF-Export |
werkzeuge.py | 58 | WERKZEUGE — Quick-Tools (Batch) |
layer_builder.py | 436 | Helper — Ebenen-Hierarchie aufbauen, Sublayer-Sync |
startup.py | 136 | Init — liest dossier.project.json, lädt Module selektiv |
Tragende Patterns
Bridge-Pattern (Pflicht für jedes Panel)
class MyBridge(panel_base.BaseBridge):
def __init__(self):
panel_base.BaseBridge.__init__(self, "mymodule")
def _on_ready(self):
self.send("STATE_SYNC", {...}) # WebView fertig geladen
def handle(self, data):
t = data.get("type")
if t == "ACTION": self._do_action()
def _bridge_factory():
b = MyBridge()
_install_listeners(b) # Rhino-Events registrieren
return b
panel_base.register_and_open(
"mymodule", "MY PANEL", PANEL_GUID_STR,
_bridge_factory,
icon_spec=("foundation", "#5fa896"), # Material-Icon + Petrol
min_size=(400, 300),
)React ↔ Python Kommunikation
- React → Python:
document.title = "RHINOMSG::{json}"— gepollt im Idle-Handler - Python → React:
bridge.send(type, payload)→webview.ExecuteScript("window.onRhinoMessage(…)") - Chunking: Messages > 200 KB werden in
panel_base.handle_rawautomatisch gesplittet und reassembliert. Subklassen kümmern sich nicht drum.
Source ↔ Volume Pattern
Jedes Smart-Element hat:
- eine Source-Geometrie (Achse / Outline / Punkt) — vom User editierbar
- ein generiertes Volume (Brep) — automatisch regeneriert bei Source-Änderungen
Beispiel Wand: Source = Achs-Linie, Volume = Brep mit Dicke × Höhe.
Sticky-Storage (Cross-Module-State)
Konventionen für sc.sticky-Keys:
"{modul}_bridge"— Bridge-Instanz"{modul}_listeners"— Bool-Flag: Listener bereits registriert?"_dossier_*"— globale States (z.B._dossier_joints_cache)"{modul}_*_cache"— Modul-Cache
Listener-Hookup (Idempotent)
def _install_listeners(bridge):
flag = "mymodule_listeners"
sc.sticky["mymodule_bridge"] = bridge
if sc.sticky.get(flag): return # Schon registriert
Rhino.RhinoApp.Idle += _on_idle
Rhino.RhinoDoc.ActiveDocumentChanged += _on_view_change
sc.sticky[flag] = TrueDatenhaltung
- Geschosse in
doc.Strings["dossier_ebenen"]als JSON - Smart-Elemente als Rhino-Objekte mit UserStrings —
dossier_element_id,dossier_element_type, … - Section-Styles über
Rhino.DocObjects.SectionStyle()+layer.SetCustomSectionStyle() - Settings:
~/Library/Application Support/ch.gabrielevarano.Dossier/dossier_settings.json
Eine .3dm-Datei bleibt eine Datei — keine externen Datenbanken.
Layer-Hierarchie
10_GRUNDRISSE
└── EG
├── 20_WAENDE
├── 30_DECKEN
├── 31_DAECHER
└── 40_TREPPEN
└── 1OG (gleiche Sublayer)
20_SCHNITTE
30_ANSICHTEN
00_RASTER · 01_VERMESSUNG · 40_SITUATION · 90_REFERENZEN · 99_KONSTRUKTIONCross-Module-Pfade
| Sender → Empfänger | Trigger | Effekt |
|---|---|---|
rhinopanel → elemente | Apply von Ebenen-Struktur | elemente_bridge._regenerate_all() regeneriert Wände/Decken |
elemente → rhinopanel | Wand/Decken-Delete | ebenen_bridge_ref._send_state() |
oberleiste → overrides | Preset-Auswahl in Topbar | overrides_bridge._send_state() |
massstab ↔ ausschnitte | Viewport-/Zoom-Wechsel | Bi-direktional Skala lesen / setzen |
gestaltung ↔ rhinopanel | Hatch-Pattern auf Selektion | Pattern + Scale + Rotation-Signatur vergleichen |
Konventionen
- Python-Identifier ohne Umlaute —
ue/oe/aestattü/ö/äin Code-Bezeichnern, Layer-Codes, UserString-Keys. UI-Strings dürfen Umlaute. LoadHtml-inline stattfile://-URL — Rhinos WKWebView blockiert sonst<script type="module">durch CORS.- TextEntity-RTF — Rhinos eingebauter Parser unterstützt nur
\b \i \ul \strike \fN \tab {}plus Newline-via-\par. Kein\fs(eine TextEntity hat global eine Schriftgröße). - Sticky-Reads immer mit
is not None-Check.
Launcher-Anbindung
Der Dossier-Launcher (launcher/) ist eine separate Tauri-2-App. IPC zu Rhino läuft dateibasiert, nicht über Socket:
- Settings:
~/Library/Application Support/ch.gabrielevarano.Dossier/dossier_settings.json - Live-Push:
pendingApplyLayout-Key,oberleiste.tick_idle()pollt und cleart - System-Tray mit Quick-Open der letzten 5 Projekte
Rhino läuft ohne Launcher, Launcher läuft ohne Rhino.