Zum Inhalt springen
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

ModulLOCRolle
panel_base.py697Fundament: BaseBridge, WebView-IO, Panel-Registration, Icons
rhinopanel.py798EBENEN — Zeichnungsebenen, Layer-Hierarchie, Presets
elemente.py7'244ELEMENTE — Wände, Decken, Öffnungen, Treppen, Tragwerk, Räume
gestaltung.py1'635GESTALTUNG — Selektions-Attribute (Farbe, Lineweight, Hatch)
oberleiste.py981OBERLEISTE — Top-Bar, Display, Massstab, Snaps, Settings
massstab.py1'096MASSSTAB — Viewport 1:N, Auto-DPI, PlotWeight
overrides.py797Engine — regelbasierte Overrides (Bedingung → Aktion)
overrides_panel.py226UI für Overrides-Engine
ausschnitte.py708AUSSCHNITTE — Viewport-Snapshots (Kamera + Display + Layer)
dimensionen.py613DIMENSIONEN — Bemassung (Wand-Dicken, Geschoss-Höhen, …)
layouts.py749LAYOUTS — Plan-Editor, Titelblock, PDF-Export
werkzeuge.py58WERKZEUGE — Quick-Tools (Batch)
layer_builder.py436Helper — Ebenen-Hierarchie aufbauen, Sublayer-Sync
startup.py136Init — 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_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)

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

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ängerTriggerEffekt
rhinopanelelementeApply von Ebenen-Strukturelemente_bridge._regenerate_all() regeneriert Wände/Decken
elementerhinopanelWand/Decken-Deleteebenen_bridge_ref._send_state()
oberleisteoverridesPreset-Auswahl in Topbaroverrides_bridge._send_state()
massstabausschnitteViewport-/Zoom-WechselBi-direktional Skala lesen / setzen
gestaltungrhinopanelHatch-Pattern auf SelektionPattern + Scale + Rotation-Signatur vergleichen

Konventionen

  • Python-Identifier ohne Umlauteue/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.