Commit Graph

40 Commits

Author SHA1 Message Date
karim 2ee4688fe3 Masse-Dropdown in Oberleiste + Satellite-Settings statt Dimensionen-Inline
User-Feedback: Mass-Style passt nicht ins Dimensionen-Panel, und der
Name "Mass-Style" gefaellt nicht. Umzug in die Oberleiste (analog Display)
+ Zahnrad oeffnet eigenes Settings-Fenster. UI-Begriff jetzt "Masse".

Frontend:
- OberleisteApp: neue Gruppe "Masse" mit Preset-Dropdown + Zahnrad-Button
  zwischen Display und Massstab
- MasseSettingsApp.jsx (neu): Satellite-Fenster mit Name/Raum-Rundung/
  Mass-Dezimalstellen/Mass-Einheit + Picker + Add/Delete
- DimensionenApp: MassStyleSection raus
- rhinoBridge: setMasseActive + openMasseSettings (Topbar);
  masseSetActive/masseSavePreset/masseDeletePreset (Settings-Fenster)

Backend:
- rhino/masse_settings.py (neu): Bridge fuer das Satellite-Fenster,
  Topics SET_ACTIVE / SAVE / DELETE, triggert regen_all_rooms + topbar refresh
- mass_style.regen_all_rooms(doc): neue cross-modul-Helper, queued
  Raum-Regen fuer alle raum_outline-Objekte
- oberleiste.py: massePresets + masseActiveId im State, SET_MASSE_ACTIVE
  + OPEN_MASSE_SETTINGS handler, Signature update
- dimensionen.py: Mass-Style-Endpoints + State raus (sind jetzt im
  OberleisteBridge bzw. MasseSettingsBridge)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 21:29:23 +02:00
karim 0b4b25cf47 Mass-Style Preset (Raum-Rundung + Dim-Format) + Rundung als Dropdown
Frontend:
- RaumProperties Rundung: 5er-Button-Reihe → Dropdown mit "Aus Mass-Style"
  als erstem Eintrag (leer = Default aus aktivem Preset uebernehmen)
- Dach-Typ + Mansarde-Variante: text-only Button-Reihen → Dropdowns
- Mass-Style-Section neu im DimensionenApp ganz oben:
  - Picker fuer aktives Preset
  - + (neu mit aktuellen Werten als Vorlage) / Loeschen
  - Inline-Editor: Name, Raum-Rundung, Mass-Dezimalstellen, Mass-Einheit

Backend (rhino/mass_style.py — neu):
- doc.Strings["dossier_mass_styles"]: JSON-Liste der Presets
- doc.Strings["dossier_mass_style_active"]: aktive Preset-ID
- list_presets/save_preset/delete_preset/get_active_id/set_active_id
- Convenience: raum_rundung_default(doc), dim_dezimalstellen_default(doc)
- Default-Presets bei erster Initialisierung: 1:50 / 1:100 / 1:500

elemente.py:
- _read_meta: raum_rundung leer wenn UserString fehlt (vorher gezwungen "0.1")
- _resolve_raum_rundung(meta, doc): per-Raum-Override > Mass-Style-Default
- _make_raum_stamp_text + state-send nutzen Resolver
- State sendet rundung (raw, kann "" sein) + rundungEffective + areaFmt
  damit React-Panel "Aus Mass-Style" anzeigen kann

dimensionen.py:
- Bridge-Endpoints MASS_STYLE_SET_ACTIVE / SAVE / DELETE
- _broadcast_raum_regen: bei Preset-Wechsel alle Raeume queuen → Stempel-
  Flaechen kommen mit neuer Default-Rundung
- _compute_state liefert massStyles + massStyleActive + signature update

NOCH NICHT verdrahtet: Mass-Linien-Formatierung (dimDezimalstellen,
dimEinheit) — Datenmodell ist da, Anwendung auf Rhino-Dimension-Renderer
folgt in einem naechsten Schritt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 21:18:15 +02:00
karim b69dd8e279 Kamera-Panel + Iso-Button in der Oberleiste
Oberleiste:
- View-Gruppe: Iso-Button neu zwischen Right und Persp
- matchView: Iso = parallel ohne orthogonalen Standard-Namen,
  Perspektive = !parallel — beide via Projektions-Flag unterschieden
  (Rhino-Viewport-Name ist oft "Perspective" fuer beide)
- Camera-Knopf (Icon: videocam) oeffnet das neue Kamera-Panel
- SET_VIEW Backend: 'Iso' faelltt auf kamera._set_iso(vp, "NE")
- OPEN_KAMERA_PANEL Handler

Kamera-Panel (neu — rhino/kamera.py + src/KameraApp.jsx):
- Viewport-Name + Projektions-Toggle (Persp/Parallel)
- 4 Iso-Quick-Buttons (NW/NE/SE/SW) — true-iso 35°/45°,
  Kamera-Distanz auto aus Szenen-BBox
- Vec3-Felder fuer Kamera-Position + Blick-Ziel (numerisch
  editierbar, m)
- Distanz read-only
- Brennweite (mm) bei Persp, Frustum-Breite (m) bei Parallel
- Zoom-Extents-Button
- Presets: speichern + anwenden + loeschen, persistiert in
  doc.Strings["dossier_kamera_presets"] (JSON)
- Eto-Form-Satelliten-Fenster (420x600) via panel_base.open_satellite_window

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 20:41:50 +02:00
karim de1fc887f5 Raum: outline + stamp + fill als Paired-Bauteil markieren
raum_outline fehlte in _PAIRED_SOURCE_TYPES, raum_stamp + raum_fill in
_PAIRED_VOLUME_TYPES. Klick auf die Raum-Linie markierte daher nur die
Linie, nicht den Stempel-Text/Fuellung — beim Verschieben blieb der Text
hinten.

Vorher hat das idle-Live-Regen wahrend dem Drag den Stempel mitgezogen
(mit Flicker). Mit dem _UT_ACTIVE_KEY-Bail in idle (9cde41b, fixt
Fenster-Glitch) ist das nicht mehr drin → wir muessen die drei sauber
ueber Rhinos eigene Selection-Sync zusammen halten.

Pairing macht: Klick auf Outline → _collect_partners markiert stamp +
fill mit. Gumball/Move verschiebt alle drei synchron. Nach Release
laeuft die idle-Regen-Queue (queued via _on_replace fuer raum_outline)
und aktualisiert die Flaechenangabe falls die Outline-Form sich
geaendert hat.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 19:12:07 +02:00
karim 9cde41b686 Idle: bail bei User-Transform (Move/Drag/Gumball) — Element bleibt zusammen
Der Idle-Handler verarbeitet Pending-Regens + Cascade-Queues. Lief bislang
auch waehrend _UT_ACTIVE_KEY (Move/Gumball/Drag), debounct nur ueber
last_replace_time. Auf Mac feuert Move aber teils via Delete+Re-Add statt
Replace → debounce greift nicht zuverlaessig, Idle koennte mitten in einem
Move ein Volume regenerieren und damit die Geometrie unter Rhinos Transform
wegziehen → einzelne Sub-Volumen blieben am alten Ort, Element zerfaellt
optisch.

Fix: idle bail bei _UT_ACTIVE_KEY. CommandEnd der Transform-Cmds sync't
Volumen selber, dort werden die Regens richtig in den Transform-Undo-Record
gefaltet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 13:31:31 +02:00
karim b14eb1e5dd Ebenen-Manager: Eye-Logik analog Geschosse + 'Alle anzeigen'-Mode
Eye-Knopf bleibt jetzt auch in „Nur aktive"-Mode sichtbar (dimmend, statt
zu verschwinden). Klick auf Eye in „Nur aktive" oder „Alle anzeigen"
wechselt automatisch zu „Ausgewählte" damit die Aktion wirkt.

Backend (layer_builder.apply_visibility): neuer e_mode 'all_force'
ueberschreibt das Eye-Flag (zeigt alle Ebenen). 'all' respektiert weiter
das Flag (= „Ausgewählte" im UI).

Frontend (EbenenManager):
- MODES: 'all_force'=„Alle anzeigen" hinzu, 'all' umbenannt zu „Ausgewählte"
- EbeneRow: eyeIcon/eyeOpacity/eyeTitle nach Mode, analog GeschossManager
- handleToggleVisible + Master-Eye: bei active/all_force → onModeChange('all')

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 13:26:25 +02:00
karim 68411a0ce9 swissALTI3D Multi-Tile-Naht: Sub-Sampling global statt tile-lokal
Bug: Jedes Tile berechnete origin_e/origin_n aus seinem eigenen Sample-
Punkt-Set und filterte Sub-Sampling-Punkte modulo factor_e relativ dazu.
Wenn das File-Ordering tile-individuell andere ersten 200 Punkte lieferte,
landete jedes Tile auf einer leicht anderen Phase im 0.5m-Raster — am
Tile-Boundary fehlten Faces / das Mesh hatte sichtbare Naht.

Fix: Phase aus dem ersten Sample-Punkt detect (e_phase = e mod raw_step).
Sub-Sampling-Filter benutzt den GLOBALEN LV95-Raster-Index
`round((e - e_phase) / raw_e_step)`. Da swissALTI3D ein globales Raster
ist, hat jedes Tile dieselbe Phase → konsistente Punkte am Boundary.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 13:24:25 +02:00
karim 5fd6aefd34 Delete-Cascade in EINEN Undo-Record — Cmd+Z stellt Wand + Volumen wieder her
Bug: Bei Wand-Loeschen lief das Delete von wand_axis als Rhino-Undo-Record A,
unsere Idle-Cascade (Volume-Delete, Oeffnungs-Cleanup) lief 500 ms spaeter
in separaten Records B/C/D. Cmd+Z popte nur den letzten Record → nur das
Volume kam zurueck, oder umgekehrt nur die Achse.

Fix: BeginUndoRecord("Element-Loeschen") in _on_command_begin fuer Delete-
Cmds. _flush_pending_cascades_sync laeuft in _on_command_end SOFORT (kein
500-ms-Wait noetig — bei explizitem Delete kommt die Source nicht zurueck)
und WAEHREND der Record offen ist. EndUndoRecord schliesst den Record.

Resultat: Rhinos User-Delete + alle Cascade-Deletes = EIN Cmd+Z.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:51:22 +02:00
karim 72e24fd512 Bulk-Op Bail-out per Objekt-Typ statt global — DOSSIER-Cascade bleibt aktiv
Regression vom letzten Commit: _on_object_deleted und gestaltung.on_delete
bailten KOMPLETT bei Bulk-Ops. Damit liefen Cascade-Cleanups nicht mehr:
- Wand-Achse geloescht → Wand-Volumen blieb orphaned
- Oeffnungs-Punkt geloescht → Loch in Wand verschwand nicht
- Curve mit Hatch geloescht → Hatch blieb als Geist liegen

Fix: Schnellfilter per UserString-Lookup VOR dem Bail.
- elemente._on_object_deleted: GetUserString(_KEY_TYPE) — nur DOSSIER-
  Sources triggern Cascade. OSM/Swisstopo-Curves haben keinen Type → cheap
  exit, kein per-Event-Overhead. Bulk-Bail entfernt.
- gestaltung.on_delete: bestehender _FILL_KEY/_FILL_OWNER_KEY-Check (line
  1540-1548) filtert non-Hatch-Objekte schon billig. Bulk-Bail entfernt.

Panel-Sync optimiert: _send_state aus on_object_deleted unterdrueckt
waehrend Bulk, einmaliger Push aus _on_command_end.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:46:21 +02:00
karim b425421fdd Swisstopo + OSM Importer + Höhenlinien + Bulk-Op Performance
Swisstopo Iter 3:
- Ortho-Drape: TIN-Mesh aus Terrain-Grid mit per-vertex UVs + PictureFrame-Material
- Project-Cache: TIFs werden neben .3dm gespeichert (SMB-shareable)
- Layer-Restruktur: 80_swisstopo/{Terrain, Luftbild} Sub-Ebenen
- TIFs direkt (kein PNG-Downsampling) für volle Auflösung
- UV-Inset gegen weisse Streifen zwischen Kacheln
- Hoehenlinien (2D, swissALTI3D) auf aktives Geschoss OKFF projiziert
- TIN-Mesh + Schichtenmodell aus Contours (separate Optionen)
- TLM3D entfernt (swisstopo liefert nur GDB/SHP, kein DXF)

OSM Importer (neu):
- rhino/osm.py: Overpass-API-Client
- src/OsmApp.jsx: React-Dialog mit Adresse + Radius + 7 Kategorien
- Strassen/Gebäude/Wasser/Wasserläufe/Parks/Wald/Fusswege (Codes 7101-7107)
- ElementeApp: PillGroup "Importer" mit Swisstopo + OSM Buttons

Sub-Ebenen — rekursiv durch hierarchische Ebenen:
- Visibility-Toggle: slimEbene rekursiv (children bleiben erhalten)
- Settings-Dialog: _find_sublayer_by_code_recursive + _replace_in_tree
- Hatch Auto-Fill: refresh_layer_fills + _fill_signature + _ebene_fill_for_layer
  alle rekursiv durch children
- EbenenSettingsApp: flattenEbenen-Helper

Bulk-Op Performance (Delete/Cut/etc.):
- _USER_BULK_CMDS + _BULK_ACTIVE_KEY Sticky-Flag
- CommandBegin: doc.Views.RedrawEnabled = False + Listener-Bail aktiv
- CommandEnd: RedrawEnabled restore + 1× Redraw + Selection-Refresh
- Bail-outs in dimensionen.on_idle/on_select, elemente._on_idle_selection,
  gestaltung.on_idle_flush/on_delete
- Verhindert das sichtbare "Runterzählen" pro Element bei Bulk-Delete

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:42:45 +02:00
karim 1e6bc68156 Ortho-PictureFrame Position: Plane-Origin als Bottom-Left (statt Center)
AddPictureFrame interpretiert plane.Origin als BOTTOM-LEFT corner, nicht
als Zentrum — width geht in +X, height in +Y vom Origin aus. Mit Center-
basierter Plane landete die Picture um (width/2, height/2) verschoben
nach oben/rechts. Fix: plane.Origin = (x_min, y_min, z_doc).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:51:02 +02:00
karim 85f09390bc Ortho-Foto sichtbar (PictureFrame) + Oberleiste-Polish
Swisstopo Ortho
- AddPictureFrame statt Mesh+Material — Rhinos eigener Picture-Pfad mit
  embedBitmap=True + selfIllumination=True macht die Textur in allen
  Display-Modi sichtbar (Wireframe / Shaded / Rendered / Raytraced)
- asMesh=False (Brep-Variante) — asMesh=True ist auf Mac Rhino 8 broken
  (alle Pictures landen am gleichen Punkt unabhaengig von der Plane)
- Per-Tile Sub-Ebenen unter 80_swisstopo (z.B. 80_swisstopo/2763-1254_Ortho)
  via dossier_ebenen JSON registriert → erscheinen im Dossier-Ebenen-Manager
  mit eigener Visibility
- target_layer_idx wird vor AddPictureFrame als Active-Layer gesetzt,
  Picture landet direkt auf richtigem Sub-Layer (Move-danach broeselte
  das Material)
- Regex-Fix in _parse_swisstopo_tile_bbox: Separator zwischen den beiden
  Coords MUSS Hyphen sein, sonst matcht es faelschlich auf `_YEAR_EAST_`
  Patterns wie `_2025_2763_`

Oberleiste
- DOSSIER. Logo (Krungthep + Petrol-Punkt) + Launcher-Version
  (via __LAUNCHER_VERSION__ Vite-Define aus launcher/package.json)
- Overrides + Kombi vertikal gestapelt, gleiche Label-Spalte + Dropdown-
  Breite → Dropdowns auf gleicher X-Linie
- View-Icons neu zugeordnet:
    Top=view_quilt (Raster), Front=north (Pfeil), Persp=view_in_ar (3D)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:44:19 +02:00
karim afb59b6626 Swisstopo Iter 2 + hierarchische Ebenen + 0-Kote m.ü.M
Swisstopo
- swissBUILDINGS3D 3.0 + Variant-Toggle (separated/solid) im Dialog
- Auto-Fallback auf 2.0 wenn 3.0-Tiles ueber 200 MB sind (Stadt-Fall)
- Defensiver Variant-Filter auf 3 Ebenen (Item, Asset, ZIP-Extract) — keine
  Doppelimporte mehr
- Auto-Skala korrigiert jetzt die importierten Objekte (×1000) statt die
  User-bbox zu schrumpfen — Buildings bleiben in m-Doc-Skala
- merge_grids: XYZ-Tiles werden vor dem Mesh-Bau vereint, kein 1m-Streifen
  zwischen Tiles mehr
- Layer-Konsolidierung: Build_*/Roof_*/Wall_*/Floor_* DWG-Source-Layer
  werden auf Sub-Sub-Layer unter 81_Swissbuildings/{Build,Roof,Wall,Floor}
  gemappt; solid-Variante landet flach direkt auf dem Parent
- 0-Kote m.ü.M (Projekt-Nullpunkt) wird beim Import als Z-Offset angewandt

Hierarchische Ebenen
- dossier_ebenen unterstuetzt jetzt 'children'-Array (rekursiv)
- layer_builder.build_layers rekursiv (Parent + Children unter jedem Geschoss)
- apply_visibility/update_layer_style/set_ebene_visible/set_ebene_locked
  walken den Tree (Sub-Sub-Layer mit gleichem Code-Prefix werden mit-gepflegt)
- EbenenManager mit Chevron-Toggle + Indent pro Level + Context-Menue-Item
  'Sub-Ebene hinzufuegen'
- rhinoBridge.applyVisibility schickt Children-Tree (nicht nur Top-Level) —
  sonst kommen Sub-Toggles nicht beim Backend an
- Visibility-Key in App.jsx rekursiv durch Children — useEffect feuert jetzt
  auch bei Sub-Eye-Toggles

0-Kote m.ü.M
- Eingabefeld im Geschoss-Settings-Dialog (projektweit)
- Speicherung als dossier_project_zero_mum in doc.Strings
- Wird im Swisstopo-Import als Z-Offset (m + doc-units) angewandt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:21:45 +02:00
karim 4111f12f32 Swisstopo Importer: STAC-API + Terrain-Mesh + Ortho-Drape (Iteration 1)
Frontend:
- src/SwisstopoApp.jsx NEU: Satelliten-Fenster mit Adresse-Suche, Radius-
  Wahl, Daten-Checkboxen (Gebäude/Terrain/Luftbild), Origin-Handling, Live-
  Log
- ElementeApp Swisstopo-Gruppe: Importer-Button + Karte-Button

Backend:
- rhino/swisstopo.py NEU: STAC-API-Client, Geocoding via swisstopo Search,
  LV95↔WGS84-Konvertierung, GeoTIFF/XYZ-Cache, mesh_from_grid + Ortho-Material
- swissBUILDINGS3D 2.0 (DXF/DWG) via STAC; Per-Tile-Filter (_NNNN-NN_)
  schuetzt vor versehentlichem Download der 3.5 GB National-Geodatabase
- swissALTI3D als XYZ-ZIP mit zipfile-Extraction, raeumliches Sub-Sampling
  statt Zeilen-Step (keine Streifen mehr im Terrain-Mesh)
- SWISSIMAGE 10cm GeoTIFF als RenderMaterial-DiffuseBitmap mit planarem
  UV-Mapping auf den Terrain-Mesh-bbox

Robustheit:
- Auto-Skala-Erkennung: Rhinos DXF-Parser scaliert je nach \$INSUNITS auf
  unerwartete Doc-Units; wir messen aus ersten 50 Objekten + snappen auf
  Zehnerpotenz (1, 0.001, 1000)
- bbox + origin_shift in doc-units (m_to_unit aus UnitScale + Auto-Detect)
- Tags via UserString dossier_swisstopo_kind=buildings/terrain fuer
  Replace-Detection bei erneutem Import desselben Gebiets
- BBox-Clip jetzt OPTIONAL (Default OFF, Checkbox) — bei InstanceReferences
  GetBoundingBox + Delete teuer
- Batch-Transform via System.Collections.Generic.List[Guid] statt
  Python-Loop (Python.NET-Overload-Match)
- Listener-Suppression in elemente.py + gestaltung.py + dimensionen.py
  via sticky dossier_swisstopo_busy — kein Per-Object-Spam mehr bei
  Selection/Add/Delete waehrend 5000+ Imports
- Auto-Zoom via view.ZoomBoundingBox(combined) statt Select-Loop
- Year-Dedupe auf Tile-Coord (Pattern YYYY oder YYYY-MM unterstuetzt) fuer
  alle Collections — aeltere Versionen werden ausgefiltert
- Download-Safety: > 200 MB wird abgebrochen + Live-Progress alle 2 MB
  mit UI-Yield via Rhino.RhinoApp.Wait()

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 18:22:48 +02:00
karim 41b6f8ac51 Vorschau-Quader fuer Fenster/Tuer + Launcher reinweiss + SectionStyle-Apply gehaertet
Elemente:
- _make_oeffnung_preview baut jetzt einen vollen 3D-Quader (12 Kanten) mit
  Wand-Dicke statt nur einer 2D-Flaeche. Glas-Diagonalen auf Vorder- und
  Hinterflaeche, Brueest-Linie (gepunktet) auf der Vorderflaeche, Achs-
  Marker auf der Wand-Achse, dazu ein 3D-Mass-Label "B x H Br" ueberm
  Sturz (zentriert, an der Wand "geheftet"). Aktualisiert live bei
  Option-Aenderungen.

Launcher:
- Paper-Theme zu reinem Weiss umgestellt (User-Feedback "zu warm"):
  --bg #ffffff, --dark #f0f0f0, neutrale Greys statt Sand-Tones,
  Schatten ohne warmen Braun-Stich. Petrol-Radial-Gradient oben raus.
- latest.json aus dem neuen Release-Build.

SectionStyle (Bug-Hunt — Hatch/Boundary haben nicht gegriffen):
- *Source-Properties (HatchColorSource, BoundaryColorSource,
  BoundaryLinetypeSource) jetzt explizit auf ColorFromObject /
  LinetypeFromObject — sonst hat Rhino die eigenen Color/Linetype-
  Werte ignoriert (Default ist ByLayer).
- doc.Layers.Modify nach SetCustomSectionStyle, sonst persistiert Mac
  Rhino den Custom-Style nicht zuverlaessig.
- Helper _try_set + _enum_int eliminieren das 8x duplizierte
  Property-Probe-Pattern.
- Property-Inventar wird einmal pro Session gedumpt (verfuegbare
  SectionStyle-Felder) damit API-Mismatches sichtbar werden.
- Per-Layer Apply-Logs zeigen welche Properties via welchem Namen
  gesetzt wurden — leicht debuggbar im Mismatch-Fall.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 12:15:02 +02:00
karim ee4d4876dd Revert Window-Layout persistent-skip (Mac Rhino persistiert nicht)
Annahme war falsch: Mac Rhino merkt sich die Window-Anordnung NICHT
zuverlaessig zwischen Sessions. User-Report: Layout ist nach Restart
"rausgeflogen". Cold-Start-Apply muss jedes Mal laufen.

- Cold-Start in _on_ready: sticky-Guard only (1x pro Rhino-Session),
  cfg.windowLayoutLastApplied entfernt
- pending in tick_idle: skip wenn sticky bereits TRUE (= cold-start
  hat in dieser Session schon applied) — verhindert doppelten Apply
  und damit die zweite Re-Mount-Welle
- _mark_layout_applied()-Helper geloescht
- _on_apply (Eto-Button) + APPLY_LAYOUT-Message auf direkten Apply
  zurueck

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 04:52:43 +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 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 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 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 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