9dc191be4f
OpenStudio-Suite Architektur-Plugin fuer Rhino 8 (Mac): - Smart-Elemente: Wand, Decke, Dach (Pult/Sattel/Walm/Mansarde), Oeffnungen (Fenster/Tueren mit Rahmen + Sims + Glas + Fluegel), Treppen (gerade · L · Wendel mit Schrittmass-Validierung) - Live-Previews mit Step-Lines + Soll-Range-Clamping - Bidirektionale Selection-Sync zwischen Source-Linie und Volume - Geschoss-/Ebenen-Verwaltung mit OKFF-Persistenz - Layouts mit PDF-Export - Ausschnitte / Massstab / Override-Regeln - Petrol-Gruen Theme (Rapport-konform) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
161 lines
6.8 KiB
Markdown
161 lines
6.8 KiB
Markdown
# RhinoPanel — Projektdokumentation für Claude
|
|
|
|
## Was ist das?
|
|
|
|
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.
|
|
|
|
## Kommunikation React ↔ Python
|
|
|
|
**React → Python:** `document.title = "RHINOMSG::{json}"` (queue-basiert, 80ms delay)
|
|
**Python → React:** `webview.ExecuteScript("window.onRhinoMessage({...})")`
|
|
|
|
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
|
|
|
|
## Datenmodell
|
|
|
|
```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"}
|
|
]
|
|
```
|
|
|
|
Gespeichert in `doc.Strings["rhinopanel_ebenen"]` (JSON). OKFF wird nur für `type: "grundriss"` berechnet (kumulativ). Schnitt/Ansicht haben keine Höhenparameter.
|
|
|
|
## 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())
|
|
```
|
|
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
|
|
|
|
Typ-Badges: GR=blau `#3a6fa8`, SC=orange `#c87050`, AN=türkis `#50c8a0`
|
|
|
|
## Workflow Panel starten
|
|
|
|
```bash
|
|
npm run build # nach Code-Änderungen
|
|
```
|
|
|
|
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`
|
|
|
|
## Nächste mögliche Schritte
|
|
|
|
- **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
|