# Dossier — Architektur Stand: 2026-05-17. Dieses Dokument beschreibt wie die Module zusammenspielen, welche Konventionen gelten und wo bekannte Schwachstellen liegen. > ⚠️ **Runtime-Realität — Migration in Arbeit:** Trotz `# ! python3` > Shebangs in allen Files läuft der Plugin-Code aktuell als **IronPython 2.7** > unter .NET 8 auf Mac Rhino 8.31 — verifiziert via `sys.version`. Migration > zu Rhinos neuer CPython-3-Engine läuft (siehe CLAUDE.md für Stand). Bis > dahin gelten IPy-2.7-Idiome im Code: `__builtin__`, kein f-string-Vertrauen, > `print` mit/ohne Klammern gemischt, `import System` direkt (statt `clr.AddReference`). **Bei jeder Code-Änderung in `rhino/` zuerst dieses Dokument lesen.** Wenn sich Patterns hier ändern, dieses Dokument mit-aktualisieren — sonst rotted es. --- ## 1. Module-Map (`rhino/`) | Modul | LOC | Rolle | Hängt ab von | |---|---:|---|---| | `panel_base.py` | 697 | **Fundament**: BaseBridge, WebView-IO, Panel-Registration, Icons, Legacy-Migration | — | | `rhinopanel.py` (EBENEN) | 798 | Zeichnungsebenen (Layer-Hierarchie, Presets) | panel_base, layer_builder, massstab (sticky) | | `elemente.py` (ELEMENTE) | **7244** | Smart Elements: Wände, Decken, Öffnungen, Treppen, Tragwerk, Räume (SIA-416) | panel_base, overrides+rhinopanel (sticky) | | `gestaltung.py` (GESTALTUNG) | 1635 | Selektions-Attribute: Farbe, Lineweight, Linetype, Hatch, Plot-Sync | panel_base, massstab (sticky) | | `oberleiste.py` (OBERLEISTE) | 981 | Top-Bar: View/Display/Massstab-Proxy, Snaps, Window-Layout, Settings | panel_base, massstab, overrides (sticky) | | `massstab.py` (MASSSTAB) | 1096 | Viewport-Skala 1:N, Auto-DPI (CoreGraphics), PlotWeight | panel_base, layer_builder | | `overrides.py` | 797 | Engine: regelbasierte Overrides (Bedingung→Aktion), Presets cross-doc | — (Library) | | `overrides_panel.py` | 226 | UI auf overrides-Engine | panel_base, overrides, oberleiste (sticky) | | `ausschnitte.py` (AUSSCHNITTE) | 708 | Viewport-Snapshots (Kamera + Display + Layer) | panel_base, massstab | | `dimensionen.py` (DIMENSIONEN) | 613 | Bemaßungs-Panel (Wand-Dicken, Geschoss-Höhen, Öffnungen) | panel_base | | `layouts.py` (LAYOUTS) | 749 | Layout-Editor für Druckplatten | panel_base | | `werkzeuge.py` (WERKZEUGE) | 58 | Quick-Tools (Batch-Operationen) | panel_base | | `layer_builder.py` | 436 | Helper: Ebenen-Hierarchie aufbauen, Sublayer-Sync | — (Library) | | `startup.py` | 136 | Initialisierer: liest `dossier.project.json`, lädt Module selektiv | panel_base + Module via `__import__` | | `clean.py` / `clean_layers.py` / `_reset_panels.py` / `inspect_section.py` | 48-163 | Wartung / Debugging | — | --- ## 2. Tragende Patterns ### 2.1 Bridge-Pattern (Pflicht für jedes Panel) ```python 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-Name + Petrol min_size=(400, 300), ) ``` ### 2.2 React ↔ Python Kommunikation - **React → Python**: `document.title = "RHINOMSG::{json}"` — gepollt im Idle-Handler in `panel_base`. - **Python → React**: `bridge.send(type, payload)` → `webview.ExecuteScript("window.onRhinoMessage(...)")` - **Chunking**: Messages > 200 KB werden in `panel_base.handle_raw` automatisch gesplittet + reassembliert. Subklassen kümmern sich nicht drum. ### 2.3 Sticky-Storage (Cross-Module-State) Konventionen für Keys: - `"{modul}_bridge"` — Bridge-Instanz (in `_bridge_factory` registriert) - `"{modul}_listeners"` — Bool-Flag: Listener bereits registriert? (verhindert Doppel-Hook) - `"_dossier_*"` — globale States (z.B. `_dossier_joints_cache`, `_dossier_timing_enabled`, `_dossier_layout_applied`) - `"{modul}_*_cache"` — Modul-Cache (z.B. `_JOINTS_CACHE_KEY` in elemente) ### 2.4 Listener-Hookup (Idempotent) ```python def _on_idle(s, e): b = sc.sticky.get("mymodule_bridge") if b is not None: # IMMER None-Check try: b._send_state() except Exception: pass 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] = True ``` ### 2.5 Cache-Pattern - **Joint-Cache** (`elemente.py: _JOINTS_CACHE_KEY`): pro Geschoss; invalidiert bei Add/Delete/Replace. - **Material-Cache** (`elemente.py`): Hex→MaterialIndex; stale-Check beim Lesen. - **Hatch-Curve-Link** (`gestaltung.py`): UUID→Hatch in Sticky, weil Rhino UserStrings bei Move/Replace teils wegwischt. - **Display-Modes-Cache** (`oberleiste.py`): einmalig gelesen, Sticky-gecacht. - **Pending-Hatch TTL** (`gestaltung.py`): 3 s Fenster nach Drag/Move, in dem Hatch-Metadaten wiederherstellbar sind. ### 2.6 Settings-File Pfad-Hierarchie: 1. **Primär** (Launcher schreibt): `~/Library/Application Support/ch.gabrielevarano.Dossier/dossier_settings.json` 2. **Legacy-Fallback** (read-only): `~/Library/Application Support/RhinoPanel/dossier_settings.json` Bekannte Keys: `windowLayout`, `autoApplyLayout`, `pendingApplyLayout`, `rhinoApp`, `templatePath`. Normalisierung: Legacy `defaultLayout` → `windowLayout` in `_settings_load`. ### 2.7 Window-Layouts auf Mac (XML, nicht .rwl!) - Speicherort: `~/Library/Application Support/McNeel/Rhinoceros/8.0/settings/Scheme__Default/workspaces/.xml` - Display-Name aus `` Attribut. - Apply: Reflection über `Rhino.UI.WindowLayout.*`, Fallback `_-SetActiveLayout "Name" _Enter`. - Live-Apply aus dem Launcher: setzt `pendingApplyLayout` im Settings-JSON; `oberleiste.tick_idle()` pollt + clearet. --- ## 3. Cross-Module-Pfade (Sticky-Bus) | Sender → Empfänger | Trigger | Effekt | |---|---|---| | `rhinopanel` → `elemente` | Apply von Ebenen-Struktur (Höhen/OKFF) | `elemente_bridge._regenerate_all()` regeneriert Wände/Decken | | `elemente` → `rhinopanel` | Wand/Decken-Delete | `ebenen_bridge_ref._send_state()` (Fallback-Chain) | | `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-Signature vergleichen | **Risiko**: Sticky-Reads ohne `is not None`-Check sind verstreut (vor allem in `oberleiste.py` an einigen Stellen). Bei Refactor immer Schutz einbauen. --- ## 4. Bekannte Schwachstellen ### 4.1 `elemente.py` Monolith (7244 LOC) Enthält Wand-Achse+Volumen, Wand-Miter/T-Junction, Decken (Brep-Extrusion), Öffnungen (Fenster/Türen mit Rahmen+Sims+Flügel), Treppen (gerade/L/Wendel), Tragwerk (Stütze/Träger/I-Profil), Räume (SIA-416 Stempel + Farben). Vorschlag für späteren Refactor: Split in `wand.py`, `decke.py`, `oeffnung.py`, `treppe.py`, `tragwerk.py`, `raum.py` + Shared-Utils-Modul (`_read_meta`, `_geschoss_lookup`, `_active_geschoss_id`). Aktuell **nicht kritisch**, aber bremst Navigation und Tests. ### 4.2 Duplizierter Code - `_color_to_hex` / `_hex_to_color` in 3 Modulen (gestaltung, panel_base, layer_builder) - Geschoss-Lookup-Helper in elemente.py (gehören in layer_builder) - Layer-Hierarchie-Aufbau split zwischen layer_builder.py und rhinopanel.py ### 4.3 Cache-Stale-Risiken - **Joint-Cache** invalidiert bei Add/Delete/Replace — Undo/Redo wird **nicht** abgefangen - **Material-Cache** erkennt erst beim Zugriff dass ein Index ungültig ist; bei Material-Delete in Rhino bleibt Cache bis Restart stale - Workaround: `clean.py` für manuellen Cache-Clear, aber nicht automatisch ### 4.4 None-Check-Lücken in Sticky-Reads Beispiel `oberleiste.py:733` (Pfad variiert): ```python b = sc.sticky.get("overrides_bridge") # kann None sein b._send_state() # → AttributeError wenn None ``` Beim nächsten Touch dieser Stellen: `if b is not None:` einziehen. ### 4.5 `ebenen_bridge_ref` Fallback-Chain Drei Lookup-Namen aus historischen Gründen (`ebenen_bridge_ref` → `ebenen_bridge` → `rhinopanel_bridge`). Bei nächstem Touch konsolidieren. ### 4.6 Doppel-Listener-Risiko Wenn zwei Module gleichzeitig laden und beide einen globalen Idle-Handler registrieren — Flag-Schutz pro Modul gut, aber kein zentrales Lock. Bisher in Praxis kein Problem. --- ## 5. Was die Architektur richtig macht (nicht anfassen ohne Grund) 1. **BaseBridge-Abstraktion** ist sauber: alle Bridges folgen demselben Lifecycle, WebView-Integration ist transparent. 2. **Chunk-Handling** für Large Messages (>200 KB) in `panel_base.handle_raw` — elegant, Subklassen merken nichts. 3. **Migration-Strategy** (`traite_` → `pause_` → `dossier_` Sticky-Prefixes): idempotent, per-Doc-Flag verhindert Mehrfach-Lauf. 4. **Icon-System**: Multi-Fallback PNG → SVG → Material-Font → Buchstabe; gecacht. 5. **Selective Module-Loading** über `startup.py` + `dossier.project.json`: ein Projekt zieht nur die benötigten Module. 6. **DPI-Auto-Detection** via CoreGraphics auf Mac — robuster als die meisten alternativen Ansätze. 7. **UTF-8 Handling konsequent**: `ensure_ascii=False`, defensive int()-Casts vor Format — vermeidet Rhino-Encoder-Bugs. 8. **Defensives Error-Handling**: Try/Except mit `[MODUL]`-Präfix-Logging in der Rhino-Konsole; Plugin bricht nicht ab bei Einzelfehlern. --- ## 6. Launcher-Anbindung (Tauri) Der **Dossier-Launcher** (`launcher/`) ist eine separate Tauri-App: - Verwaltet Projekt-Liste + Settings + Updates (auto via `tauri-plugin-updater`) - Schreibt `dossier_settings.json` in den oben (§2.6) genannten Primär-Pfad - Live-Push an laufende Rhino-Session: `pendingApplyLayout`-Key in Settings, `oberleiste.tick_idle()` pollt + clearet - System-Tray mit Quick-Open der letzten 5 Projekte (`refresh_tray_menu`-Command nach jedem Recent-Update) Rhino kann ohne Launcher laufen; Launcher kann ohne Rhino laufen. IPC ist bewusst dateibasiert, kein Socket. --- ## 7. Wenn du was änderst 1. **Lies dieses Dokument** + den `## tragenden Patterns`-Block der CLAUDE.md 2. **Halt dich an die Naming-Konventionen** (Sticky-Keys, Bridge-Factory) 3. **Bei Sticky-Reads: `is not None`-Check** (siehe §4.4) 4. **Cache invalidieren wenn dein Code Source-Daten ändert** (siehe §2.5) 5. **Dieses Dokument up-to-date halten** wenn sich Patterns/Schwachstellen ändern