Commit Graph

82 Commits

Author SHA1 Message Date
karim 5f5ed531b5 Launcher: Paper-Theme (warmes Weiss + schwarze Schrift + Petrol-Akzent)
Vom Dark-Theme komplett auf hellen Paper-Look umgestellt — fuehlt sich
weniger nach IDE und mehr nach Notizbuch an, weniger ermuedend.

- --bg #f4f0e6 warmes Papier · --surface #fff Cards · --dark #ebe5d4 cream
- --text #1a1a1a fast-schwarz · sekundaere Greys in warmen Toenen
- --border in Sandsteinfarben (#d8d0bc) statt anthrazit
- Schatten warm-braeunlich rgba(70,55,30) statt schwarz
- button.primary: weisse Schrift auf Petrol (statt dunkel)
- Dialog-Backdrop dunner + warmer (rgba(40,30,15,0.35))
- Petrol-Akzent (#5fa896) bleibt — wirkt auf Papier sogar praesenter

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 04:49:28 +02:00
karim 0caa0f9813 pendingApplyLayout: auch skippen wenn marker matched
Log nach E zeigte ZWEI Apply-Auslöser hintereinander:
1. Cold-Start in _on_ready (setzt marker) → 4 Panels remountet
2. pendingApplyLayout vom Launcher (~Sekunden später) → applied erneut
   weil pending-Pfad keinen marker-Vergleich machte → 8 Panels remountet

→ doppelte Re-Mount-Welle, riesiger versteckter Cost.

Fix: gleicher marker-Vergleich wie im _on_ready-Pfad. Wenn der gewuenschte
Layout-Name bereits persistent angewendet wurde, skippen + pending-Flag
trotzdem aus settings.json poppen.

Force-Apply-Use-Case (User klickt "Jetzt anwenden" im Launcher): Launcher
kann den marker vorher leeren (windowLayoutLastApplied="") um Re-Apply zu
erzwingen — bisher nicht im Launcher implementiert, kommt bei Bedarf.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 04:43:35 +02:00
karim ce81d42916 Window-Layout Auto-Apply: skip wenn bereits persistent angewendet
Cold-Start-Perf: das Auto-Apply von DOSSIERUIV0.2 lief bei jedem Rhino-
Start einmal — Rhino's Window-Layout-Restore mountet dabei mehrere
Panels neu (zweite Mount-Welle, ~70 ms gemessen + Rhino-internes Re-
Layout). Rhino merkt sich die Window-Anordnung aber selbst persistent
nach dem ersten erfolgreichen Apply.

Loesung: zweite Persistenz-Ebene neben sticky.
- sticky["_dossier_layout_applied"] bleibt fuer process-lifetime
  (verhindert Endlos-Loops bei Re-Mounts)
- cfg["windowLayoutLastApplied"] in dossier_settings.json — bei naechstem
  Cold-Start wird verglichen: wenn name unveraendert → skip Apply-Cmd
- _mark_layout_applied() helper schreibt nach erfolgreichem Apply
- Alle 4 Apply-Pfade umgestellt: _on_ready (cold-start), open_settings_dialog
  (Eto-Button), APPLY_LAYOUT-Message (React), pendingApplyLayout (Launcher)

Edge cases:
- User aendert Layout-Name → last_applied != name → Re-Apply trotzdem
- User klickt "Jetzt anwenden" → marker wird aktualisiert
- Mac Rhino verliert Window-Anordnung → User muss manuell triggern
  (settings-button "Jetzt anwenden"); flag verhindert nicht das, nur
  das automatische Re-Apply

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 04:40:05 +02:00
karim 222b00c113 Zeichnungsmanager Master-Controls + Scheren + Startup-Perf + Oeffnung-Preview
UI:
- GeschossManager: Master-Eye + Master-Lock im Header (analog EbenenManager).
  Scheren-Button pro Geschoss togglet hasClipping. Auge ganz links wie bei
  Ebenen. Eye-Logik klar 4-Wege: aktive Z immer hell+on, in 'active'/'all_force'
  fuer non-active gedimmt, sonst spiegelt Flag direkt. Schrift wird NIE gegrayt.
  Neuer Mode 'all_force' = "Alle anzeigen" (ignoriert Eye), 'all' jetzt mit
  Label "Ausgewaehlte" (respektiert Eye). Klick aufs Auge in 'active'/'all_force'
  wechselt automatisch in "Ausgewaehlte" damit Aktion sofort wirkt.

- layer_builder.apply_visibility: neuer z_mode 'all_force' vor visible-Check —
  zeigt jede Z auch wenn Eye=false war.

- elemente._cmd_create_oeffnung: gruene Live-Preview (vertikales Oeffnungs-
  Rechteck + Breiten-Marker + Diagonale) waehrend Fenster/Tuer-Platzierung
  entlang der Wand-Achse. Brueest-Offset aus Geschoss-UK korrekt im Z.

Performance Cold-Start:
- panel_base: Inlined-HTML als Modul-Cache (1x build, n-mal mount). Pro
  Panel-Mount nur noch str.replace + LoadHtml. Spart bei 10 Panels 9x
  ~395 KB Disk-Read + Regex-Pass. Cache-Key = mtime von dist/index.html.

- Timing-Instrumentierung: _t_mark + print_startup_summary. Hook in startup.py
  feuert 3s nach Plugin-Load + listet Wall-time, Top-10, Aggregat pro Phase.

- OberleisteBridge: Command-Enumeration (~1000 Commands) jetzt lazy via
  Rhino.RhinoApp.Idle statt synchron im __init__. Cold-Start nicht blockiert,
  Autocomplete kommt ~1 Frame spaeter.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 04:36:56 +02:00
karim 95031ee2c0 Panels poliert: Ebenenkombi in Oberleiste, Satelliten-Dialoge, Caps weg, Perf
- Ebenenkombination raus aus Ebenen-Panel, in Oberleiste-Topbar +
  Editor-Satellite (AusschnittLayerDialog embedded). doc.Strings
  haelt active_comb_name, auto-clear bei manueller Eye/Lock-Aenderung.
- EbenenSettingsDialog jetzt Satellite mit Ebene-Picker-Dropdown
  (auto-save on switch via SAVE_KEEP).
- Per-Ausschnitt Einstellungen-Satellite (Massstab, Display, Overrides,
  Ebenenkombi). Alte 'Sichtbarkeit bearbeiten'-Option entfernt.
- Layouts/Ausschnitte: Top-Header weg, Sticky-Footer mit Anzahl +
  Aktionen. LayoutDialog ist jetzt Satellite mit Format-Live-Preview.
- Panel-Captions + Default-Ebenen-Namen auf Mixed-Case (Ausschnitte,
  Ebenen, Waende ...). Nur DOSSIER bleibt caps.
- DimensionenApp: Card-Optik raus, REF-Wuerfel mit Kreisen statt
  Quadraten + Hover-Scale.
- GeschossManager angeglichen an EbenenManager: Rechtsklick-Menue,
  Lock-Button, Delete-X, Duplizieren. layer_builder honoriert z.locked.
- Active Sublayer folgt jetzt dem Geschoss-Wechsel (gleicher Code
  unter neuem Parent).

Performance Geschoss-Wechsel:
- elemente._send_state() ersetzt durch _notify_active_geschoss()
  (Partial-Push statt 200+ Elements re-enumerieren).
- _apply_visibility dedupe via sticky last-applied-signature
  (STATE_SYNC-Echo loopt nicht mehr durch alle Layer).
- _update_clipping nur wenn alt oder neu hasClipping=True.
- Redundante doc.Views.Redraw() im CPlane-Pfad entfernt — die folgende
  apply_visibility-Roundtrip redrawt 30ms spaeter ohnehin.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 03:58:28 +02:00
karim e3918cb155 Overrides-Fenster aufgeräumt + Rule-Templates
UX-Cleanup:
- Globaler AN/AUS-Toggle entfernt — den gibt's bereits in der
  Oberleiste, doppelt war redundant.
- Reload/Refresh-Button entfernt — Backend re-applied automatisch
  bei jeder Regel-Aenderung, manuelles Reload nicht noetig.
- + (Neue Regel) wurde aus dem Header in eine neue Sektion
  UNTER der Kombinationen-Card verschoben.

Neues Feature: Rule-Templates (einzelne wiederverwendbare Regeln)
- Storage: ~/Library/.../override_rule_templates.json (cross-doc,
  parallel zu den Kombinationen-Presets)
- API in overrides.py: list/save/load/delete_rule_template
- Bridge-Messages: SAVE_RULE_TEMPLATE, DELETE_RULE_TEMPLATE,
  ADD_FROM_TEMPLATE
- State enthaelt jetzt ruleTemplates: [{name, rule}]

UI:
- Neuer Bereich "Neue Regel" unter Kombinationen: [+ leer] +
  [+ Aus Vorlage ▼ dropdown]
- Vorlage waehlen → insert auf hoechste Prio (gleich wie addRule)
- Im Dropdown unten: "🗑 <name> loeschen" zum Entfernen einer Vorlage
- Im Rule-Kontextmenue: neuer Eintrag "Als Vorlage speichern…"
  fragt nach Name, speichert die Regel cross-doc

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 02:10:31 +02:00
karim 42d9c1e27b Overrides als Satelliten-Fenster vom Oberleiste-Gear öffnen
OVERRIDES war als gedocktes Panel zu schmal. Jetzt: kein Panel mehr,
sondern ein echtes Rhino-Fenster (Eto.Form + WebView, frei verschieb-
und resizable), das vom Oberleiste-Gear-Button geoeffnet wird.

panel_base.open_satellite_window:
- Akzeptiert jetzt optional einen `bridge`-Parameter. Wenn gegeben,
  wird die Custom-Bridge (z.B. OverridesBridge) statt der einfachen
  inline SAVE/CANCEL-Bridge benutzt. So koennen vollwertige Panels
  (mit bidirektionalem Mess-Verkehr) als Satellite-Fenster laufen.

overrides_panel.py:
- register_and_open entfaellt — Overrides wird nicht mehr als Panel
  registriert.
- Neue Funktion open_as_window(): erstellt OverridesBridge, registriert
  sie in sticky["overrides_bridge"] und oeffnet als Satellite-Window.
  Listener werden lazy beim ersten Aufruf installiert
  (_ensure_listeners_once).

oberleiste.py:
- OPEN_OVERRIDES_PANEL ruft jetzt overrides_panel.open_as_window()
  statt RhinoUI.Panels.OpenPanel().

OberleisteApp.jsx:
- Settings-Gear (ToolButton mit icon="settings") nach dem Preset-
  Dropdown im Overrides-Bereich. Click ruft openOverridesPanel() →
  oeffnet das Satelliten-Fenster.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 01:39:07 +02:00
karim b1b2090b3e Satelliten-Dialoge: embedded-Mode + GeschossDialog auch als echtes Fenster
Zwei Dinge:

1. embedded-Mode in den Dialog-Komponenten — wenn TRUE, kein Backdrop +
   keine MaxWidth-Constraint, das Dialog fuellt das ganze WebView statt
   wie ein kleines zentriertes Fenster IN dem WebView gerendert zu werden
   (= "Fenster im Fenster"-Effekt). Betroffen:
   - GeschossSettingsDialog
   - EbenenSettingsDialog
   - GeschossDialog
   Satelliten-Apps (GeschossSettingsApp, EbenenSettingsApp,
   GeschossDialogApp) passen jetzt `embedded` durch.

2. GeschossDialog (= der grosse Mehrfach-Editor hinter dem Pencil-Button)
   laeuft jetzt auch als Satelliten-Fenster — selbe Architektur wie die
   Settings-Dialoge. Backend hat neuen Handler _open_geschoss_dialog und
   neuen Message OPEN_GESCHOSS_DIALOG. Auf Save: ganze z-Liste replace
   + _apply(save_z=True).

GeschossManager braucht den inline-Dialog-State nicht mehr; Pencil-Button
ruft openGeschossDialog(zeichnungsebenen).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 01:30:28 +02:00
karim 1ba0bda429 Settings-Dialoge in echten Rhino-Fenstern (Eto.Form + WebView)
Statt Overlay-im-Panel oeffnet sich der Settings-Dialog jetzt als
echtes Rhino-Fenster (verschiebbar, resizable, mehrere parallel).

Infrastruktur in panel_base.py:
- load_inline akzeptiert jetzt `params` (dict) und injiziert sie
  als window.PANEL_PARAMS — Satelliten-Apps lesen ihren initialen
  State daraus.
- Neue Funktion open_satellite_window(mode, params, title, size,
  on_save, on_cancel): erstellt Eto.Forms.Form mit eingebetteter
  WebView, eigenem Inline-Bridge fuer SAVE/CANCEL-Messages, ruft
  Callbacks auf und schliesst das Fenster.

Backend rhinopanel.py:
- Neue Message-Handler OPEN_GESCHOSS_SETTINGS und OPEN_EBENEN_SETTINGS.
- _open_geschoss_settings: oeffnet das Satelliten-Fenster mit dem
  Geschoss als Payload. on_save: replace im doc.Strings z-Liste +
  _apply(save_z=True).
- _open_ebenen_settings: gleich, aber fuer Ebene + hatchPatterns.

Neue React-Entries:
- GeschossSettingsApp.jsx: wrappt GeschossSettingsDialog, liest
  window.PANEL_PARAMS, schickt SAVE/CANCEL direkt via document.title-
  Bridge.
- EbenenSettingsApp.jsx: gleich fuer EbenenSettingsDialog.

main.jsx-Switch erweitert um 'geschoss_settings' und 'ebenen_settings'.

GeschossManager und EbenenManager:
- Inline-Dialog-State und -Rendering entfernt.
- onSettings ruft jetzt openGeschossSettings(z) / openEbenenSettings(e)
  in der Bridge auf → Backend oeffnet das Satelliten-Fenster.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 01:15:12 +02:00
karim e6a39531f4 Zeichnungsebenen-+: Standard = Zeichnung statt Geschoss
User-Wunsch: das '+' im Zeichnungsebenen-Panel soll standardmaessig
eine NICHT-Geschoss-Zeichnung anlegen (z.B. Bemassung, Plangrafik,
Möblierung). Wer ein Geschoss will:
- Row-Settings-Cog -> isGeschoss umschalten, ODER
- Bearbeiten-Dialog (Pencil) -> Geschoss direkt anlegen

addQuick erstellt jetzt {isGeschoss: false, name: 'Zeichnung N'}.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:56:45 +02:00
karim d6c09d22f7 Hatch↔Curve-Sync: User-Transform-Skip war zu aggressiv
Symptom: Beim Verschieben einer Polylinie ging die gekoppelte Hatch
nicht mehr mit; umgekehrt zog die Hatch die Curve auch nicht mehr
mit. Die bidirektionale Hatch-Curve-Kopplung war kaputt.

Ursache: ich hatte vor einiger Zeit in gestaltungs on_replace,
on_delete und on_add jeweils einen `_dossier_user_transform_active`-
Skip eingebaut um waehrend elemente-Moves die Listener stumm zu
halten (Performance). Damit wurden aber auch die Hatch-Coupling-
Updates fuer normale Polylinien geblockt — die laufen ja gerade
genau dann, wenn der User einen `_Move` macht.

Fix: Skip-Logik selektiver machen:

- on_replace: Skip ENTFERNT. Stattdessen ein frueher Bail-out wenn
  das Objekt weder _FILL_KEY noch _FILL_OWNER_KEY hat → Wand-Sub-
  Volumen werden immer noch nicht angepackt, aber Hatch-gekoppelte
  Polylinien laufen durch (auch waehrend _Move).
- on_delete: Skip ENTFERNT. Der vorhandene Bail-out auf "kein Hatch-
  UserString" filtert dossier-Sub-Volumen weiterhin raus. Hatch-
  gekoppelte Curves machen Cascade-Delete + Pending-Save fuer die
  Recovery in on_add.
- on_add: zwei Phasen — Drag-Recovery (Phase 1, IMMER) und Auto-Fill
  (Phase 2, nur ausserhalb User-Transform). So funktioniert die
  Delete+Add-Recovery von Rhinos Move waehrend Auto-Fill nicht
  versehentlich neue Hatches fuer Wand-Volumen anlegt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:54:51 +02:00
karim fcbc97b608 Ebene-Add Race: SET_VISIBILITY-Roundtrip cancelt APPLY → neuer Eintrag weg
Root-Cause für 'Ebene erscheint kurz und verschwindet wieder, kein
APPLY im PY-Log':

1. User klickt + → addNew. setEbenen(18 Eintraege). state local = 18.
2. visibilityKey aendert sich (ebenen-Aenderung) → applyVisibility
   debounced 30ms.
3. structureKey aendert sich → applyAll debounced 200ms.
4. T=30ms: SET_VISIBILITY landet beim Backend ZUERST.
5. `_apply_visibility` liest e_full (17 alte Eintraege) aus
   doc.Strings, merged Visibility-Flags vom Slim-Payload, schreibt
   die 17 ALTEN zurueck nach doc.Strings (der neue 18. Eintrag ist
   im merged-Loop nicht dabei weil iteriert ueber e_full).
6. broadcast STATE_SYNC mit 17 Eintraegen.
7. React-App empfaengt → setEbenen(17) → neue Ebene weg aus state.
8. structureKey wieder == appliedStructureKey → useEffect's
   clearTimeout cancelt den 200ms-applyAll-Timer.
9. APPLY feuert nie. Backend bleibt auf 17.

Fix in _apply_visibility: detect pending structural change (Payload
hat IDs/Codes die noch nicht in doc.Strings sind) und in dem Fall
das SetString-Save UND den _broadcast_state ueberspringen.
apply_visibility (Rhino-Layer-Visibility-Update) laeuft trotzdem
mit dem merged-state — die noch nicht gespeicherte Ebene hat eh
keinen Rhino-Layer und damit keine Visibility zu setzen.

Sobald der 200ms-applyAll feuert: build_layers + Save bringt alles
in Sync. Daraufhin broadcastet APPLY normal an beide Panels.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:49:36 +02:00
karim 581f366437 Ebene/Zeichnungsebene Erzeugen: window.prompt raus (macOS WKWebView-Bug)
Symptom: erster +-Klick hat funktioniert, alle weiteren nicht (kein
APPLY im Log). Diagnose: macOS WKWebView in Rhino blockiert
WIEDERHOLTE window.prompt-Aufrufe — der erste zeigt einen Dialog,
nachfolgende returnen direkt null. Mein `if (!name) return`
beendet dann ohne Add → kein APPLY → User sieht nichts.

Plus: das nach Anlegen direkt onActiveChange(code) erzeugte einen
SET_ACTIVE_LAYER race (Layer war noch nicht durch build_layers
erstellt → Warning "Sublayer mit Code X nicht gefunden").

Beide Probleme weg durch:

- EbenenManager.addNew: silent append mit Default-Name "NEU",
  Code-Feld bekommt autoEdit-Fokus → User kann direkt tippen,
  Tab springt zum Name-Feld. Code = nextFreeAfter(activeCode) bleibt
  (eine nach der aktiven).
- GeschossManager.addQuick: silent append mit Default-Name
  "NOG" (basiert auf Anzahl Geschosse), kein onActiveChange.
  User editiert ueber den Geschoss-Settings-Cog am Row falls
  noetig.

Tradeoff: keine Dialog-Bestätigung mehr wie ursprünglich vom User
gewünscht — aber dafür funktioniert's überhaupt. Falls Dialog
wieder gewünscht, müsste ein React-Modal statt window.prompt her.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:43:45 +02:00
karim f14f84ca36 Ebene/Zeichnungsebene nach Anlegen direkt aktiv setzen
Wahrscheinliche Wurzel von "kann nicht erstellen": neue Ebene
wurde zwar zur Liste hinzugefuegt, aber nicht aktiv markiert
→ keine Pill-Highlight → User sah sie nicht (vor allem wenn sie
am Listen-Ende auftauchte) und dachte es funktioniert nicht.

Fix: nach addNew/addQuick `onActiveChange(code|id)` aufrufen.
Die neue Ebene wird in der Liste als aktiv markiert (Pill-Highlight),
ist sofort auffaellig + neue Geometrie landet direkt darauf.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:36:15 +02:00
karim d63bca1ad8 Ebene-Add: Code = activeCode + nächste freie Nummer + Debug-Logs
Feature: neue Ebenen kriegen jetzt einen Code direkt nach dem
aktuell angewaehlten (z.B. active="20" → "21" oder naechste freie
darunter). Vorher war's Max+1 → sprang auf "100", neue Ebene landet
am Listen-Ende und wirkte „unsichtbar" weil weit unten.

Debug-Logs eingebaut um zu diagnostizieren warum Anlegen aus User-
Sicht nicht funktioniert:
- [EBENEN-UI] addNew → bei jedem Click + im Ebenen-Panel
- [ZEICHNUNGSEBENEN-UI] addQuick → bei jedem Click + im Z-Panel
- [EBENEN-UI/ZEICHNUNGSEBENEN-UI] structureKey diff → wenn der
  Auto-Apply-useEffect feuert
- [EBENEN-UI/ZEICHNUNGSEBENEN-UI] applyAll firing now → wenn der
  Debounce-Timer am Ende den Backend-Call macht
- [EBENEN-BE] APPLY from mode=X → Backend-Receiver
- [EBENEN-BE] mode=X: y from doc.Strings n=N → was aus doc.Strings
  als Fallback geladen wurde

So sehen wir wo's stockt — UI feuert nicht, Debounce klemmt,
Backend kriegt's nicht, oder build_layers schmeisst still.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:29:46 +02:00
karim 10690f4514 Ebene + Zeichnungsebene Erzeugen: Dialog mit Name-Input (wie Kombi)
UX-Beschwerde: addNew/addQuick haben still im Hintergrund eine Ebene
am Listenende hinzugefuegt. User sah das Ergebnis nicht weil scroll-
abhaengig oder ueberhaupt — "kann gar nicht erstellen". Bei Ebenen-
Kombinationen geht's via window.prompt, das ist klar.

Beide auf gleiche Prompt-UX umgebaut:

EbenenManager.addNew:
- Prompt "Name für neue Ebene:" mit Default "NEU"
- Bei Cancel/leer: kein Add
- Bei OK: Ebene mit UPPERCASE-Name + auto-generiertem Code, Code-
  Feld bekommt sofort den Edit-Mode (User kann ihn schnell anpassen).

GeschossManager.addQuick:
- Prompt "Name für neue Zeichnungsebene (Geschoss):" mit Default
  basiert auf Anzahl Geschosse (z.B. "3OG" wenn 2 vorhanden).
- Bei OK: neuer Eintrag als Geschoss (isGeschoss=true) mit hoehe=3.0
  und schnitthoehe=1.0 — die Default-Werte. Wegen recalcOkff zeigt
  er automatisch das richtige OKFF.

Vorher war addQuick auf `isGeschoss=false` (= Beschriftungs-Eintrag,
kein Geschoss), das war ein Konventions-Mismatch zur User-Erwartung.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:16:36 +02:00
karim c4c9e56b2c Beide Ebenen-Panels: Section-Header raus, Action-Buttons in Sichtbarkeit-Row
Der Section-Header mit dem Panel-Namen ("Ebenen" / "Zeichnungsebenen")
war redundant — der Panel-Tab in Rhino hat den Namen schon. Stattdessen
direkt mit der Sichtbarkeit-Dropdown beginnen.

EbenenManager:
- `<Section>`-Wrapper entfernt, Section-Import raus.
- "+"-Button neben der Sichtbarkeit-Dropdown (flex: 1).

GeschossManager:
- Gleich. "+"- und Edit-Buttons (Bleistift) neben der Sichtbarkeit-
  Dropdown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:02:26 +02:00
karim cbabc12064 Ebenen-Split: applyVisibility null-safe machen
App.jsx ruft jetzt `applyVisibility(null, [], activeCode, ebenen, null, eMode)`
— die fremde Slice ist leer/null weil das Panel sie nicht besitzt. Das
crashte mit "Cannot read properties of null (reading 'id')" weil der Code
`a.activeZ.id` blind dereferenzierte. Resultat: "Script error." in
window.onerror → leere Panel-UI.

Fix: Array.isArray-Guard + null-Check fuer activeZ. Backend mergt
fehlende Felder mit doc.Strings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 23:55:13 +02:00
karim e96de793a9 Ebenen-Split: FIRST_RUN-Race + Slice-Authoritaet fixen
Symptom: Nach Split funktionierten die Ebenen nicht und Container
erschienen doppelt.

Two bugs:

1. FIRST_RUN-Detection war global statt mode-aware:
   - Beim ersten Plugin-Start war doc.Strings leer.
   - Bridge A (z.B. Zeichnungsebenen) sah leer → sendete FIRST_RUN
     → React schickte APPLY mit `z=INITIAL_Z, e=[]`.
   - Backend speicherte `[]` fuer dossier_ebenen → fortan war
     doc.Strings nicht mehr leer.
   - Bridge B (Ebenen) sah dossier_ebenen vorhanden ("[]") → sendete
     STATE_SYNC mit leerer Liste statt FIRST_RUN → React-App
     ueberschrieb INITIAL_EBENEN mit `[]` → leere UI.
   Fix: `_on_ready` prueft jetzt mode-spezifisch ob SEINE Slice in
   doc.Strings ist. "ebenen"-Mode schaut auf dossier_ebenen,
   "zeichnungsebenen" auf dossier_zeichnungsebenen.

2. APPLY ueberschrieb fremde Slice mit Fallback-`[]`:
   - Wenn nur eine Panel-Slice im Payload kam, las Backend die
     andere aus doc.Strings (= leer beim ersten Mal) und schrieb
     dann *beide* Slices, davon eine als `[]`.
   - Naechstes READY sah die `[]` → STATE_SYNC statt FIRST_RUN →
     Daten weg.
   Fix: `_apply` bekommt `save_z`/`save_e` Flags. Jedes Panel ist
   autoritativ fuer SEINE Slice. APPLY aus dem Ebenen-Panel
   speichert NUR dossier_ebenen (save_z=False), aus Zeichnungs-
   ebenen NUR dossier_zeichnungsebenen.

Effekt: Wenn Ebenen-Panel zuerst lädt → speichert ebenen, lässt z
unangetastet → Zeichnungsebenen-Panel sieht z fehlt → bekommt
FIRST_RUN → schickt INITIAL_Z → speichert z. Symmetrisch wenn
Zeichnungsebenen zuerst lädt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 23:50:21 +02:00
karim a458b4c47d Ebenen-Panel in zwei UI-Panels splitten: Zeichnungsebenen + Ebenen
UX: Geschoss-Liste und globale Layer-Liste lebten bisher in einem
Panel und mussten beide gescrollt werden. Jetzt zwei getrennte Tabs.

Backend (rhino/rhinopanel.py):
- Selbe EbenenBridge-Klasse, zwei Mode-Instanzen ("ebenen" +
  "zeichnungsebenen"). Beide registrieren sich in sticky-Slots
  (`ebenen_bridge_ref` / `zeichnungsebenen_bridge_ref`).
- `_broadcast_state(doc)` Helper: liest aktuell Zustand aus doc.Strings
  und schickt STATE_SYNC an beide Bridges. Wird nach jeder state-
  aendernden Aktion gefeuert (apply, set_active_zeichnungsebene,
  toggle_clipping, remove/update ebene, layer-table-event).
- `handle(APPLY)`: wenn nur eine Slice (z oder e) im Payload, fehlende
  aus doc.Strings nachladen → Backend baut mit vollem Zustand.
- `_apply_visibility`: zMode/eMode/activeId/activeCode aus Payload
  ODER aus doc.Strings (dossier_z_mode/dossier_e_mode/dossier_active_id/
  dossier_active_code) faellen lassen — Split-Sends werden korrekt
  gemergt.
- Layer-Table-Event broadcastet jetzt statt nur das eine Panel zu
  benachrichtigen.
- Zweite `register_and_open("zeichnungsebenen", ...)` Zeile mit eigener
  GUID + Icon "levels".

Frontend:
- Neues src/ZeichnungsebenenApp.jsx: enthaelt nur GeschossManager,
  haelt Zeichnungsebenen + activeId + zMode lokal, schickt
  applyAll([z], []) und applyVisibility mit leerer Ebenen-Slice.
- src/App.jsx geschrumpft: nur noch EbenenManager + AusschnittLayer-
  Dialog. Haelt Ebenen + activeCode + eMode + Combinations. Schickt
  applyAll([], [e]) und applyVisibility mit leerer Z-Slice.
- src/main.jsx: neuer case fuer mode="zeichnungsebenen" → lädt
  ZeichnungsebenenApp.

Existierende User mit altem DOSSIERUI.rhw Workspace muessen das neue
Panel einmal manuell oeffnen (Rechtsklick Panel-Area → Panel hinzu-
fuegen → "Zeichnungsebenen"); Rhino persistiert die Anordnung danach.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 23:42:02 +02:00
karim 53fea10cba Clipping: Geschoss-Wechsel loescht Plane nicht mehr
Bug: React's SET_ACTIVE-Message schickt nur Minimal-Payload
`{id, name, isGeschoss, okff}` — ohne hasClipping/schnitthoehe.
`_update_clipping` las `enabled = active_z.get("hasClipping")` aus
diesem Minimal-Payload → False → Plane geloescht. Beim Zurueck-
wechseln auf EG mit aktiviertem Clipping war die Plane weg
obwohl der Toggle im Panel weiter „aktiv" zeigte.

Fix: `_update_clipping` nimmt nur die `id` aus dem uebergebenen
Hint (oder aus `dossier_active_id`) und holt sich den vollen
Geschoss-Record aus `dossier_zeichnungsebenen` doc.String. Damit
sind hasClipping, schnitthoehe, hoehe, visible immer verfuegbar.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 23:26:23 +02:00
karim 15185568ce Mirror/Copy: Duplikat-IDs in CommandEnd umbenennen statt verkoppelt zu lassen
Rhinos Mirror/Copy/Array kopiert selektierte Objekte mit ihren UserString-
Metadaten → Duplikat-IDs im Doc (z.B. zwei `wand_axis` mit gleicher
`id=wall_xxx`). Resultat: unser System sieht beide als „dasselbe Element",
fasst sie verkoppelt an, Pure-Transform wird konfus, Original wand_volume
wandert mit weil bb-snapshot matched.

Fix in `_on_command_end`, BEVOR Pure-Transform-Detection laeuft:

1. Snapshot speichert jetzt `obj_ids`-Set aller pre-Command Rhino-Object-Ids.
2. Pass A: alle neuen Sources (obj.Id nicht im Snapshot) deren UserString-id
   bereits in `sources_snap` existiert → identifiziert als Mirror/Copy-
   Duplikat, neue UUID generiert (gleicher Prefix wie bei Original-Erzeugung).
3. Pass B: alle neuen Volumes mit id = alter-renamed-Source → bekommen die
   neue ID + `oeff_parent` wird umgehaengt wenn ihre Eltern-Wand renamed.
4. Pass C: neue oeffnung_points kriegen `oeff_parent` auf renamed Wand
   umgehaengt.
5. Pass D: alle gesammelten Renames atomar via ModifyAttributes anwenden.

Resultat: Mirror-Kopie ist nach CommandEnd ein vollstaendig eigenstaendiges
Element mit eigenen IDs + intakter Parent-Cascade. Pure-Transform sieht
saubere Snapshot-vs-aktuell-Bilanz (Originale=Identity, Kopien außerhalb
des Snapshots → keine Action erforderlich, Rhino hat sie schon geometrisch
korrekt platziert).

Funktioniert generisch fuer Mirror, Copy, Array — alle dup-id-erzeugenden
Operationen. Im Log: `[ELEMENTE] mirror/copy-Duplikate: N Objs neu-ID'd`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 23:17:39 +02:00
karim 82bd15a074 Pure-Transform: Rotation läuft jetzt instant wie Translation
Erweitert die bisherige Pure-Translate-Optimierung auf beliebige
Rigid-2D-Transforms (Translation + Z-Rotation). Statt nur einen
Delta-Vektor zu detektieren, wird pro Source ein Rigid-Transform aus
Snapshot-vs-aktueller-Geometrie berechnet:

- Curve-Sources: aus Endpunkten Drehwinkel + Translation ableiten.
- Length-Aenderung der Curve → Scale/End-Grip → abort_pure.
- Z-Aenderung der Curve → Z-Drag → abort_pure (UK_OVER-Schreibung
  geht weiter ueber Regen-Pfad).
- Point-Sources: nur Translation aus Position.

Konsistenz-Check: alle Curve-Transforms muessen identisch sein,
Point-Positionen muessen `canonical(old_pos) == new_pos` erfuellen.
Sonst → Regen.

Bei pure_transform != None: Transform auf alle Geometries der
Cascade anwenden die nicht schon von Rhinos Move/Rotate
transformed wurden. Volumes via bb-Snapshot-Check, Sources via
identity-transform-check.

Resultat: einzelne Wand + Oeffnungen rotieren → instant statt
~100-200ms Regen.

Mirror-Limitation: Einzelne Wand-Spiegelung wird als 180°-Rotation
interpretiert (matched die Endpunkte). Bei symmetrischen Volumen
unsichtbar; bei asymmetrischen Fenstern visuell anders als ein
echter Mirror. Mehrere Walls gleichzeitig spiegeln triggert
all_consistent=False → Regen-Fallback (korrekt). Bekannte
Einschraenkung, separater Fix nötig.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 23:02:20 +02:00
karim 0978d9fc2e Rotation: Snapshot-basierte Migrate für korrekte Bogenlängen-Position
Bug: Bei Rotation um einen externen Punkt liegt der Öffnungs-Punkt
nach Rhinos Transform NICHT mehr auf der alten Achse → migrate's
ClosestPoint(current_pos) snappte zum nächsten Endpunkt der alten
Achse → relative=1 → alle Öffnungen landeten am gleichen Ende der
neuen Achse (= „bei Referenzpunkt der Drehung").

Fix: Migrate nutzt jetzt die PRE-TRANSFORM Position aus dem Snapshot
(via `old_positions` Parameter). Aufrufer im CommandEnd-Regen-Pfad
sammelt die alten Positionen aus `sources_snap` und gibt sie weiter.

Migrate setzt opening_point.Z jetzt auch konsistent auf
`wall_uk + brüstung` statt nur `brüstung` — vermeidet Brüstung-Drop
beim nachfolgenden _apply_oeffnung_constraint.

Constraint überspringt XY-Projektion wenn Wand gerade migriert
wurde (`_dossier_migrated_walls` sticky-Set) — sonst würde
ClosestPoint(pt_old) auf neuer rotierter Achse die Position wieder
verschieben.

Debug-Logs in _apply_wand_z_drag_constraint + Wand-Regen bleiben
drin — haben bei der Eingrenzung des UK_OVER-Bugs geholfen, kosten
nichts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 22:47:09 +02:00
karim 2a75b1da93 Snapshot: Transform-Hierarchie + Brüstung-Konvention + Undo-Record
Funktionierender Stand der Move/Rotate-Pipeline mit Eltern-Kind-Cascade
und sauberer Brüstung-Semantik:

- Pure-Translate hierarchisch: nur Sources mit echtem Delta + ihre Kinder
  (Öffnungen → Wand) folgen mit. Wand folgt NICHT der Öffnung.
- Orphan-Detection: Öffnung ohne mitbewegter Eltern-Wand → Regen-Fallback
  (sonst bleibt Cutout am alten Ort im Wand-Brep).
- Brüstung = relativ zur Wand-UK (Archicad/Revit-Konvention). Bei Wand-
  Z-Drag wird UK_OVER angepasst, Brüstung bleibt; Öffnungs-Punkt wandert
  via Snapshot+Delta mit. Keine Doppel-Addition mehr.
- Opening-Punkt wird beim Erzeugen direkt auf UK+brüstung platziert
  (sonst Brüstung-Drop beim ersten Move).
- Undo-Record umschliesst Rhinos Move + unseren Regen in einem Cmd+Z-
  Schritt → keine doppelten Elemente nach Undo.
- RedrawEnabled-Suppression event-getriggert (erst beim ersten Replace-
  Event nach User-Klick) → Rubber-Band + Drag-Vorschau bleiben sichtbar.
- _Undo/_Redo: Event-Handler komplett aussetzen → kein Regen-Storm.
- Gestaltung-Listener während User-Transform + Regen stumm, danach
  einmaliger Selection-Refresh.

Enthält Debug-Logs in _apply_wand_z_drag_constraint + Wand-Regen
für offenen Bug: bei gemeinsamer Z-Verschiebung (Wand+Fenster+Tür)
landen Öffnungen manchmal über der Wand — UK_OVER scheint nicht
durchzukommen. Logs sollen das eingrenzen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 22:20:35 +02:00
karim d3984ba501 Perf: Pure-Translate-Skip — Wand+Tür+Fenster gemeinsam = instant
Im CommandEnd-Batch wird jetzt zuerst auf pure-translate gecheckt:
- Alle bewegten Source-Geometrien haben EXAKT denselben Delta-Vektor
- Beide Endpunkte einer Wand-Achse haben gleichen Vektor (= kein End-Grip-
  Drag, kein Rotate/Scale)
- Keine Z-Komponente in den Deltas (= kein Brüstungs-Property-Change)

Wenn alle Bedingungen erfüllt: REINE TRANSLATION aller Geometrien um den
Delta-Vektor. Sub-Volumen die schon von Rhinos Multi-Select-Move
transformed wurden (BBox-Center verschoben) werden geskippt; nur die
unbewegten kriegen Translate(vec). KEIN Wand-Regen, KEIN Boolean-Diff →
instant.

Snapshot um Volume-BBox-Centers erweitert für die Same-Position-
Detection. Bei Property-Änderung (Brüstung/Höhe/Breite/Rotate) fällt's
automatisch auf den vollen Regen-Pfad zurück.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 14:58:20 +02:00
karim 8a67b9f9d1 Perf: Wand-Cutout BooleanDifference batchen + 0.001 Toleranz
`_regenerate_element_body` Wand-Pfad: alle Öffnungs-Cutouts werden jetzt in
EINEM BooleanDifference-Call pro Wand-Schicht subtrahiert (statt N einzelne
Calls). Bei einer 3-schichtigen Wand mit 2 Öffnungen: 3 Boolean-Ops statt 6.

Plus: Toleranz auf 0.001 m (= 1 mm) festgesetzt. Architektur-Genauigkeit
reicht, und Boolean-Diff läuft mit dieser Toleranz spürbar schneller als
mit feineren Werten.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 14:22:04 +02:00
karim 8f5084b085 Audit-Cleanup: doppelte Brüstungs-Mitnahme entfernt + dead files raus
elemente.py:
- Idle-Pfad Brüstungs-Mitnahme entfernt — war Duplikat zur CommandEnd-Logik
  und konnte je nach Reihenfolge entweder doppelt anwenden oder gar nicht
  (race condition mit `_elemente_wand_z_delta` Sticky-Reset).
- `float(z_delta)` mit try/except für ValueError/TypeError gewrapped —
  vorher konnte ein korruptes Sticky-Tuple den Idle/CommandEnd-Pass crashen.
- `_elemente_replace_selected_ids` wird nach Migrate consumiert (auf None
  gesetzt). Sonst blieb eine stale Liste hängen und beeinflusste spätere
  unverwandte Migrations.
- Einrückung im CommandEnd-Brüstungs-Block normalisiert.

Dead Files:
- `rhino/startup.py3` entfernt — veraltetes Backup ohne Marker-Code für den
  Launcher-Splash. `rhino/startup.py` ist die aktuelle Version.
- `rhino/__pycache__` aufgeräumt (war eh in .gitignore).

Kein funktionales Verhalten geändert. Audit-Findings HIGH/MEDIUM bereinigt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 14:06:07 +02:00
karim 2dde46cb85 Snapshot 2: Move/Rotate-Performance + Hatch↔Curve bidirektional
Performance-Optimierungen für User-Transform-Commands (_Move/_Rotate/etc):
- CommandBegin/End-Listener: Mein Code schläft während Rhinos Transform läuft
  komplett (Replace-Handler early-return). Beim CommandEnd vergleicht ein
  batch-Pass den Pre-Transform-Snapshot mit dem aktuellen State und macht
  EINEN konfliktfreien Sync-Regen pro betroffener Wand. Kein "Unable to
  transform"-Konflikt mehr, deutlich snappier.
- Sub-Volumen non-destruktiv: doc.Objects.Replace statt Delete+AddBrep wenn
  die Anzahl gleich bleibt (= häufiger Fall bei Brüstung/Höhe/XY-Drag).
- Migrate-Skip bei reinem Z-Drag: spart die Pass durch alle Öffnungen wenn
  XY unverändert ist.
- Sync-Regen-Deduplizierung im Batch via _dossier_skip_sync_regen Flag.
- Display-Suppress während des gesamten CommandEnd-Batch (kein sichtbares
  „Aufbauen" von Sub-Volumen).

Plus Gestaltung-Fix:
- Hatch→Curve Reverse-Sync via Hatch.Get3dCurves(outer=True): User kann die
  Hatch alleine verschieben/rotieren/skalieren → Curve folgt mit derselben
  Transform. Vorher nur Curve→Hatch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 13:21:16 +02:00
karim 961b3c0396 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>
2026-05-18 01:50:45 +02:00
karim 1180d7bedf Release 0.1.0
Initial Dossier-Launcher Release mit signiertem Update-Bundle.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 03:06:12 +02:00
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