Stable working state after a long iteration session. The plugin now supports:
- Multi-Surface-Select für alle Element-Typen (Türen/Fenster/Treppen/Tragwerk)
- Wand-Z-Drag → unbound mode (UK/OK-Override, Wand vom Geschoss entkoppelt)
- Wand-Z-Drag nimmt verknüpfte Öffnungen mit (Brüstung += delta_z via Idle-Pfad)
- Öffnungs-XY-Drag snapt direktional auf Wand-Tangente
- Öffnungs-Z-Drag passt Brüstung an (Fenster sofort sync, Tür deferred)
- Wand-Delete kaskadiert Öffnungen (deferred via Idle, robust gegen _Rotate/_Move)
- Source-Cascade beim Öffnungs-Delete (deferred analog Wand-Kaskade)
- Listener-Cleanup robust gegen _reset_panels.py Reload (Refs in
_dossier_runtime_event_refs gespeichert, vor Re-Install deregistriert)
- _count_same_id_type filtert IsDeleted (verhindert Source-Duplikat-Bug bei Move)
- Frontend: Brüstungs-Slider für Tür ("Schwelle"), Flügel-Block nur bei Fenster
Plus aus früherer Phase dieser Session:
- Dossier-Launcher Auto-Load via Rhinos StartupCommands-XML
- Default-Pfad zeigt auf gebundeltes startup.py (out-of-the-box für neue User)
- Splash-Window beim Plugin-Load mit native macOS rounded corners
- Diverse Launcher-Verbesserungen (Brüstungs-Default, tauri.conf, capabilities)
Known issue: bei Multi-Select-Move mit vielen Sub-Volumen kann sporadisch
"Unable to transform" auftreten (Rhinos Move-Operation kollidiert mit Wand-
Regen). Tür-spezifischer Defer-Pfad mildert das, Fenster läuft sync.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
11 KiB
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
# ! python3Shebangs in allen Files läuft der Plugin-Code aktuell als IronPython 2.7 unter .NET 8 auf Mac Rhino 8.31 — verifiziert viasys.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,import Systemdirekt (stattclr.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)
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 inpanel_base. - Python → React:
bridge.send(type, payload)→webview.ExecuteScript("window.onRhinoMessage(...)") - Chunking: Messages > 200 KB werden in
panel_base.handle_rawautomatisch 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_factoryregistriert)"{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_KEYin elemente)
2.4 Listener-Hookup (Idempotent)
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:
- Primär (Launcher schreibt):
~/Library/Application Support/ch.gabrielevarano.Dossier/dossier_settings.json - 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/<GUID>.xml - Display-Name aus
<RhinoUI name="...">Attribut. - Apply: Reflection über
Rhino.UI.WindowLayout.*, Fallback_-SetActiveLayout "Name" _Enter. - Live-Apply aus dem Launcher: setzt
pendingApplyLayoutim 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_colorin 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.pyfür manuellen Cache-Clear, aber nicht automatisch
4.4 None-Check-Lücken in Sticky-Reads
Beispiel oberleiste.py:733 (Pfad variiert):
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)
- BaseBridge-Abstraktion ist sauber: alle Bridges folgen demselben Lifecycle, WebView-Integration ist transparent.
- Chunk-Handling für Large Messages (>200 KB) in
panel_base.handle_raw— elegant, Subklassen merken nichts. - Migration-Strategy (
traite_→pause_→dossier_Sticky-Prefixes): idempotent, per-Doc-Flag verhindert Mehrfach-Lauf. - Icon-System: Multi-Fallback PNG → SVG → Material-Font → Buchstabe; gecacht.
- Selective Module-Loading über
startup.py+dossier.project.json: ein Projekt zieht nur die benötigten Module. - DPI-Auto-Detection via CoreGraphics auf Mac — robuster als die meisten alternativen Ansätze.
- UTF-8 Handling konsequent:
ensure_ascii=False, defensive int()-Casts vor Format — vermeidet Rhino-Encoder-Bugs. - 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.jsonin 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
- Lies dieses Dokument + den
## tragenden Patterns-Block der CLAUDE.md - Halt dich an die Naming-Konventionen (Sticky-Keys, Bridge-Factory)
- Bei Sticky-Reads:
is not None-Check (siehe §4.4) - Cache invalidieren wenn dein Code Source-Daten ändert (siehe §2.5)
- Dieses Dokument up-to-date halten wenn sich Patterns/Schwachstellen ändern