Snapshot: Wand/Öffnung Multi-Surface-Select + Z-Drag + Brüstungs-Mitnahme
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>
This commit is contained in:
@@ -1,160 +1,147 @@
|
||||
# RhinoPanel — Projektdokumentation für Claude
|
||||
# Dossier — Projekt-Anweisungen für Claude
|
||||
|
||||
## Was ist das?
|
||||
## Was das ist
|
||||
|
||||
Ein React-Plugin für Rhino 8 (Mac) das als schwebendes Fenster läuft und Architektur-Workflows aus Vectorworks/ArchiCAD nachbildet. Die React-UI wird in Rhinos Eto.Forms WebView über `LoadHtml` (inline) eingebettet.
|
||||
**Dossier** ist ein Rhino 8 Plugin (Mac) mit React-WebView-Panels für
|
||||
architektonische Workflows (Wände, Decken, Öffnungen, Räume, SIA-416,
|
||||
Plan-Layouts). Teil der OpenStudio-Suite. Schwester-App: Rapport.
|
||||
|
||||
## Kommunikation React ↔ Python
|
||||
**Dossier-Launcher** ist eine separate Tauri-App (`launcher/`), die Projekte
|
||||
verwaltet, Settings hält (auch für den Plugin), Auto-Updates liefert und im
|
||||
System-Tray lebt.
|
||||
|
||||
**React → Python:** `document.title = "RHINOMSG::{json}"` (queue-basiert, 80ms delay)
|
||||
**Python → React:** `webview.ExecuteScript("window.onRhinoMessage({...})")`
|
||||
## Runtime: Python 3.9 CPython (verifiziert 2026-05-17)
|
||||
|
||||
Nachrichten-Typen:
|
||||
- `APPLY` — Ebenen auf Rhino anwenden, GH triggern
|
||||
- `LAYER_VISIBILITY` — Layer sofort ein/ausblenden
|
||||
- `LAYER_LOCK` — Layer sperren/entsperren
|
||||
- `SET_ACTIVE` — Aktiven Rhino-Layer setzen
|
||||
- `STATE_SYNC` — Python → React, beim Panel-Start
|
||||
Dieser Ordner `DOSSIER/` läuft mit Rhinos **neuem Python-3-Engine** (CPython
|
||||
3.9.10 via Script Editor). Der Vorgänger `rhino-panel/` bleibt **frozen** als
|
||||
IPy-2.7-Referenz.
|
||||
|
||||
## Datenmodell
|
||||
**Wie geladen wird (WICHTIG):**
|
||||
- ✅ **`_ScriptEditor`** → Datei öffnen + Run-Button → CPython 3.9 (Shebang
|
||||
`#! python3` wird respektiert). Das ist der **funktionierende Pfad**.
|
||||
- ✅ **Rhino Options → General → Command Lists → „Run these commands every
|
||||
time a model is opened"** → Form: `_-RunPythonScript "/voller/pfad.py"`
|
||||
(mit Dash + Quotes). Persistiert in `Options/General/StartupCommands` der
|
||||
`settings-Scheme__Default.xml`. Lädt das Skript bei jedem Rhino-Start
|
||||
silent, ohne File-Dialog. Trotz Dash bleibt Shebang `#! python 3`
|
||||
wirksam → CPython 3.9 (verifiziert 2026-05-17 Mac Rhino 8). Der Launcher
|
||||
trägt diesen Eintrag automatisch ein.
|
||||
- ⚠️ `_-RunPythonScript "path.py"` mit Dash in der **interaktiven Command-
|
||||
Line** → IronPython 2.7 (Legacy). NICHT benutzen für DOSSIER, sonst
|
||||
crasht die SectionStyle-Logik etc.
|
||||
- ⚠️ `_RunPythonScript path.py` ohne Dash — in der Command-Line OK (Py3
|
||||
via Shebang, ohne Quotes, in einer Zeile). Im StartupCommands-Feld
|
||||
öffnet diese Form aber einen File-Dialog statt zu laufen.
|
||||
|
||||
```json
|
||||
[
|
||||
{"id": "eg", "name": "EG", "type": "grundriss", "hoehe": 3.50, "schnitthoehe": 1.00, "okff": 0.00},
|
||||
{"id": "1og", "name": "1OG", "type": "grundriss", "hoehe": 3.00, "schnitthoehe": 1.00, "okff": 3.50},
|
||||
{"id": "saa", "name": "Schnitt A-A", "type": "schnitt"},
|
||||
{"id": "nor", "name": "Nordansicht", "type": "ansicht"}
|
||||
]
|
||||
**Migration-Stand:**
|
||||
- ✅ Repo kopiert nach `DOSSIER/`, alle Pfade umgestellt
|
||||
- ✅ Shebangs aller Files auf `#! python3` (ohne Space) — Format das Rhino erkennt
|
||||
- ✅ Code war bereits Py3-syntax-kompatibel (kein xrange/iteritems/unicode())
|
||||
- ✅ Plugin läuft als CPython 3.9.10, alle 9 Panels registrieren
|
||||
- ✅ Reflection.Emit + Eto.Forms + Bridge funktionieren
|
||||
- ✅ **`Rhino.DocObjects.SectionStyle()` instanziierbar** + `layer.SetCustomSectionStyle()`
|
||||
verfügbar — die volle Section-Style-API ist jetzt zugänglich (Anlass der Migration)
|
||||
|
||||
## Bei Code-Arbeit — Reihenfolge
|
||||
|
||||
1. **`ARCHITECTURE.md` zuerst lesen** — Module-Map, Konventionen, Schwachstellen.
|
||||
2. **Dann das relevante Modul lesen**. Nicht raten was drin steht.
|
||||
3. **Erst danach editieren**.
|
||||
|
||||
## Anti-Over-Engineering
|
||||
|
||||
Diese Regeln sind nicht verhandelbar — Verstöße kosten Zeit beim Aufräumen.
|
||||
|
||||
- **Keine Abstraktionen einführen, die ein konkretes Problem lösen.**
|
||||
Drei ähnliche Zeilen sind besser als eine Hilfsfunktion mit drei Aufrufstellen.
|
||||
- **Kein „aufräumen drumherum".** Ein Bugfix bleibt ein Bugfix.
|
||||
- **Kein Error-Handling für Szenarien, die nicht eintreten können.**
|
||||
- **Keine Feature-Flags, keine Migrations-Shims parallel zur alten Funktion.**
|
||||
- **Keine Kommentare die WAS sagen** — nur WARUM-Kommentare wenn nicht-offensichtlich.
|
||||
- **Keine erfundenen Module/Funktionen/Flags.** Erst `grep`, dann editieren.
|
||||
|
||||
## Anti-Patterns (aus echten Sessions)
|
||||
|
||||
- **`try/except: pass` als Bug-Verstecker.** Lieber `print("[MODUL] err:", ex)` und weiter.
|
||||
- **Sticky-Reads ohne `is not None`-Check.** Siehe `ARCHITECTURE.md §4.4`.
|
||||
- **„Cleveres" Refactoring von Wand-Geometrie.** `elemente.py` (7244 LOC)
|
||||
enthält BIM-Logik die in Echtbau-Projekten läuft. NIE in einem Rutsch
|
||||
modularisieren ohne expliziten Auftrag + Test-Plan.
|
||||
- **Mac vs. Windows Rhino-Pfade verwechseln.** Mac Rhino 8 speichert
|
||||
Window-Layouts als **XML in `Scheme__Default/workspaces/<GUID>.xml`**, nicht
|
||||
als `.rwl`.
|
||||
- **Python-Runtime annehmen statt prüfen.** Diagnose mit `print(sys.version)`
|
||||
— das hat uns 4 Wochen versteckte IPy2.7 gekostet.
|
||||
- **`document.title` mit Umlauten kaputt machen.** UI-Strings dürfen Umlaute;
|
||||
**Python-Backend bevorzugt `ue/oe/ae`** in Identifiern, Layer-Codes,
|
||||
UserString-Values.
|
||||
|
||||
## Python-Konventionen — POST-MIGRATION TARGET
|
||||
|
||||
Sobald die Migration durch ist, gilt:
|
||||
|
||||
- Datei-Header: `# ! python3` + `# -*- coding: utf-8 -*-` (werden dann wirksam)
|
||||
- **Aufruf in Rhino:** `_RunPythonScript "path"` (ohne Dash!) — sonst startet
|
||||
IPy 2.7. Alternative: `_-ScriptEditor` mit Code-Engine
|
||||
- `print(x)` — IMMER mit Klammern (Python-3-Style)
|
||||
- `builtins` statt `__builtin__`
|
||||
- f-strings erlaubt: `f"value: {x}"`
|
||||
- CLR: `import clr; clr.AddReference("System.Drawing"); from System.Drawing import Color`
|
||||
- UI-Strings dürfen Umlaute, Code-Identifier nicht (`tuer`, nicht `tür`)
|
||||
|
||||
## Build & Reset (Cheatsheet)
|
||||
|
||||
```bash
|
||||
# Rhino-Panels Frontend
|
||||
npm run build # im Repo-Root
|
||||
|
||||
# Launcher Frontend
|
||||
cd launcher && npm run build
|
||||
|
||||
# Launcher Backend Check
|
||||
cd launcher/src-tauri && cargo check
|
||||
|
||||
# Launcher Release (signiert, schreibt latest.json)
|
||||
cd launcher && ./scripts/release.sh
|
||||
|
||||
# Python-Syntax-Check (kein Rhino nötig)
|
||||
python3 -c "import ast; ast.parse(open('rhino/elemente.py').read())"
|
||||
|
||||
# Runtime-Verify in Rhino (welcher Python-Engine läuft wirklich?)
|
||||
_RunPythonScript /Users/karim/STUDIO/DOSSIER/rhino/startup.py
|
||||
# Erste Zeile sollte zeigen: [STARTUP] Python: 3.x ... nach Migration
|
||||
```
|
||||
|
||||
Gespeichert in `doc.Strings["rhinopanel_ebenen"]` (JSON). OKFF wird nur für `type: "grundriss"` berechnet (kumulativ). Schnitt/Ansicht haben keine Höhenparameter.
|
||||
**Plugin reset in Rhino** (nach Python-Änderungen):
|
||||
|
||||
## Rhino Layer-Hierarchie
|
||||
|
||||
```
|
||||
10_GRUNDRISSE
|
||||
└── EG
|
||||
├── 01_WAND (schwarz, lw 0.50)
|
||||
├── 02_TUER_FENSTER (blau, lw 0.25)
|
||||
├── 03_MOEBEL (grau, lw 0.13)
|
||||
├── 04_TEXT (hellgrau, lw 0.13)
|
||||
├── 05_TREPPEN (gold, lw 0.35)
|
||||
└── 06_3D_VOLUMEN (lila, lw 0.25)
|
||||
└── 1OG (gleiche Sublayer)
|
||||
20_SCHNITTE
|
||||
└── Schnitt A-A
|
||||
├── 21_PROFIL (rot, lw 0.70)
|
||||
├── 22_WAND (orange, lw 0.25)
|
||||
└── 23_TEXT (hellgrau, lw 0.13)
|
||||
30_ANSICHTEN
|
||||
└── Nordansicht
|
||||
├── 31_FASSADE (türkis, lw 0.35)
|
||||
└── 32_TEXT (hellgrau, lw 0.13)
|
||||
00_RASTER, 01_VERMESSUNG, 40_SITUATION, 90_REFERENZEN, 99_KONSTRUKTION
|
||||
```
|
||||
|
||||
## Dateistruktur
|
||||
|
||||
```
|
||||
rhino-panel/
|
||||
├── src/
|
||||
│ ├── App.jsx # Hauptkomponente, State-Management
|
||||
│ ├── main.jsx # Entry point, window.onerror handler
|
||||
│ ├── index.css # Dark theme, CSS-Variablen (Swiss minimal)
|
||||
│ ├── lib/
|
||||
│ │ └── rhinoBridge.js # Kommunikation React↔Python
|
||||
│ └── components/
|
||||
│ ├── EbenenManager.jsx # Zeichnungsebenen-Liste (GR/SC/AN Badges)
|
||||
│ ├── EbenenDialog.jsx # Dialog zum Bearbeiten der Ebenen
|
||||
│ ├── LayerPanel.jsx # Layer-Sichtbarkeit/Sperre togglen
|
||||
│ ├── BottomBar.jsx # "Auf Rhino anwenden" Button
|
||||
│ └── Section.jsx # Ausklappbarer Abschnitt
|
||||
├── rhino/
|
||||
│ ├── rhinopanel.py # Panel starten, Bridge, LoadHtml-Inline
|
||||
│ ├── layer_builder.py # Rhino-Layer erstellen/aktualisieren
|
||||
│ └── INSTALL.md # Setup-Anleitung inkl. GH
|
||||
├── dist/ # Gebaute App (npm run build)
|
||||
└── vite.config.js # base: './' wichtig für file:// URLs
|
||||
```
|
||||
|
||||
**Alte Dateien (nicht mehr aktiv, können gelöscht werden):**
|
||||
- `src/components/GeschossManager.jsx` — ersetzt durch EbenenManager
|
||||
- `src/components/GeschossDialog.jsx` — ersetzt durch EbenenDialog
|
||||
|
||||
## Kritische technische Details
|
||||
|
||||
### Warum LoadHtml statt file:// URL
|
||||
Rhinos WKWebView blockiert `<script type="module">` bei `file://` URLs (CORS/module restrictions). Lösung: Python liest `dist/index.html`, inliniert CSS und JS, lädt via `webview.LoadHtml(html)`. JS wird in `DOMContentLoaded` eingewickelt damit `#root` existiert wenn React rendert.
|
||||
|
||||
### Warum Idle-Event für URL-Loading
|
||||
`LoadHtml` in `__init__` läuft bevor der WebView in einem Fenster ist → React kann nicht in DOM rendern. Fix: `Rhino.RhinoApp.Idle` Event wartet bis Fenster sichtbar ist.
|
||||
|
||||
### Warum kein Docking
|
||||
`RegisterPanel` schlägt fehl: `"constructor must accept uint, RhinoDoc or no params"`. IronPython `*args` befriedigt Rhinos .NET-Reflection nicht. Panel läuft daher als schwebendes `forms.Form`.
|
||||
|
||||
### IronPython 2 Limitierungen
|
||||
- `# -*- coding: utf-8 -*-` Header in allen .py Dateien
|
||||
- Keine Umlaute in Strings (ue/oe/ae statt ü/ö/ä)
|
||||
- `e.Title` statt `webview.Title` im DocumentTitleChanged Handler
|
||||
|
||||
## Grasshopper-Anbindung
|
||||
|
||||
Wenn "Anwenden" gedrückt wird:
|
||||
1. `layer_builder.build_layers()` erstellt/aktualisiert Rhino-Layer
|
||||
2. JSON wird in `doc.Strings["rhinopanel_ebenen"]` gespeichert
|
||||
3. `canvas.Document.NewSolution(True)` triggert GH-Neuberechnung
|
||||
|
||||
**GH Python-Komponente (eine Komponente, Output `a`):**
|
||||
```python
|
||||
import json, Rhino, Rhino.Geometry as rg
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
raw = doc.Strings.GetValue("rhinopanel_ebenen")
|
||||
ebenen = json.loads(raw) if raw else []
|
||||
a = []
|
||||
for e in ebenen:
|
||||
if e.get("type") != "grundriss": continue
|
||||
idx = doc.Layers.FindByFullPath("10_GRUNDRISSE::{}::01_WAND".format(e["name"]), -1)
|
||||
if idx < 0: continue
|
||||
for obj in doc.Objects.FindByLayer(doc.Layers[idx]):
|
||||
crv = obj.Geometry
|
||||
if not isinstance(crv, rg.Curve): continue
|
||||
c = crv.DuplicateCurve()
|
||||
c.Transform(rg.Transform.Translation(0, 0, e.get("okff", 0)))
|
||||
ext = rg.Extrusion.Create(c, e["hoehe"], c.IsClosed)
|
||||
if ext: a.append(ext.ToBrep())
|
||||
_RunPythonScript /Users/karim/STUDIO/DOSSIER/rhino/_reset_panels.py
|
||||
```
|
||||
Für Live-Updates: GH `Timer` Komponente (1000ms) mit Python-Komponente verbinden.
|
||||
|
||||
## Design-System
|
||||
|
||||
Dark theme, Swiss minimal, inspiriert von rapport.kgva.ch.
|
||||
CSS-Variablen in `index.css`. Wichtigste:
|
||||
- `--bg-base: #1c1c1e` — Hintergrund
|
||||
- `--accent: #5a9e5a` — Grün (Anwenden-Button)
|
||||
- `--active: #3a5f8a` — Blau (aktive Ebene)
|
||||
- `--font: 'Inter'` — via Google Fonts
|
||||
- Petrol-Grün `--accent: #5fa896` (Hauptakzent), dunkles Petrol `#2f5d54`
|
||||
- Hintergrund `--bg: #0e1413`
|
||||
- Fonts: Krungthep/Archivo Black für „DOSSIER"-Logo, Playfair Display für
|
||||
Headings, DM Mono für Body
|
||||
- Konsistent zur Website (`/Users/karim/STUDIO/DOSSIER-WEBSITE/`)
|
||||
|
||||
Typ-Badges: GR=blau `#3a6fa8`, SC=orange `#c87050`, AN=türkis `#50c8a0`
|
||||
## Settings-Files (Pfade)
|
||||
|
||||
## Workflow Panel starten
|
||||
- **Launcher → Rhino IPC** (file-based, kein Socket):
|
||||
`~/Library/Application Support/ch.gabrielevarano.Dossier/dossier_settings.json`
|
||||
- Legacy-Fallback (read-only): `~/Library/Application Support/RhinoPanel/dossier_settings.json`
|
||||
- Launcher-Cache: `~/Library/Application Support/ch.gabrielevarano.Dossier/recent.json`
|
||||
|
||||
```bash
|
||||
npm run build # nach Code-Änderungen
|
||||
```
|
||||
## Vorgänger-Codebase
|
||||
|
||||
In Rhino (bei Änderungen am Python-Code):
|
||||
```python
|
||||
import scriptcontext as sc
|
||||
sc.sticky["rhinopanel_registered"] = False
|
||||
sc.sticky["rhinopanel_form"] = None
|
||||
```
|
||||
→ `_RunPythonScript` → `rhinopanel.py`
|
||||
Der alte Ordner `/Users/karim/STUDIO/rhino-panel/` bleibt als **read-only
|
||||
Referenz** stehen. Wenn ein Feature dort funktioniert das hier noch nicht
|
||||
portiert ist — dort schauen, dann hier neu in Python-3-Style implementieren.
|
||||
**Nicht** einfach kopieren, sondern beim Übertragen den Migrations-Style
|
||||
anpassen.
|
||||
|
||||
## Nächste mögliche Schritte
|
||||
## Wenn unklar — fragen, nicht raten
|
||||
|
||||
- **Docking**: Rhino 8 RhinoCode (Python 3) statt IronPython verwenden — hat andere Panel-Registration API
|
||||
- **Live-Vorschau**: Rhino `ObjectAdded`/`ObjectModified` Events in rhinopanel.py für automatischen GH-Trigger ohne Anwenden-Button
|
||||
- **Schnittlinien**: Für SC-Ebenen Schnittlinie/Position als Parameter hinzufügen
|
||||
- **Layerpanel dynamisch**: LayerPanel.jsx zeigt aktuell statische Layer-Gruppen — soll dynamisch aus `ebenen`-State kommen
|
||||
- **2D-Fills**: Schraffuren/Füllungen für Wände (Vectorworks-ähnlich)
|
||||
- **Viewport-Zuweisung**: Grundrisse/Schnitte/Ansichten einem Rhino-Detailbild zuweisen
|
||||
Wenn die Aufgabe ambig ist oder Konsequenzen über die offensichtlichen
|
||||
hinausgehen: erst nachfragen.
|
||||
|
||||
Reference in New Issue
Block a user