Files
DOSSIER/CLAUDE.md
T
karim 9dc191be4f Initial commit — Dossier Rhino 8 Plugin
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>
2026-05-16 04:27:41 +02:00

6.8 KiB

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

[
  {"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):

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

npm run build   # nach Code-Änderungen

In Rhino (bei Änderungen am Python-Code):

import scriptcontext as sc
sc.sticky["rhinopanel_registered"] = False
sc.sticky["rhinopanel_form"] = None

_RunPythonScriptrhinopanel.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