---
title: Architektur
linkTitle: Architektur
weight: 2
---

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)

```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 + 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_raw` automatisch gesplittet und reassembliert. Subklassen kümmern sich nicht drum.

### Source ↔ Volume Pattern

Jedes Smart-Element hat:

1. eine **Source-Geometrie** (Achse / Outline / Punkt) — vom User editierbar
2. 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)

```python
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
```

## Datenhaltung

- **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

```text
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_KONSTRUKTION
```

## Cross-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/ae` statt `ü/ö/ä` in Code-Bezeichnern, Layer-Codes, UserString-Keys. UI-Strings dürfen Umlaute.
- **`LoadHtml`-inline** statt `file://`-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.
