User: B/I/U Toggle-Buttons hinken hinterher oder zeigen invertiert.
Im WYSIWYG sieht alles richtig aus aber Rhino zeigt nur die LETZTE
gesetzte Schriftart fuer alles.
Fix 1 — B/I/U sync:
- selectionchange-Listener pollt jetzt queryCommandState fuer
bold/italic/underline und syncen das an Toolbar-State
- toggleBold/Italic/Underline machen nur noch exec() — kein manuelles
setBold(b => !b) mehr (war out-of-sync wenn execCommand wegen
fehlender Selection nicht griff)
- B/I/U-Button-Highlight reflektiert jetzt die echte Cursor-Position
Fix 2 — RTF nimmt nur letzte Schrift:
Reihenfolge im _commit war falsch. Vorher: RichText → TextHeight →
_apply_font → _apply_align. _apply_font setzt te.Font (Default-Font)
NACH dem RichText → schiesst die per-Run-Fonts in der RTF tot.
Neue Reihenfolge:
1. PlainText (Initial-Content)
2. TextHeight, Font, Align (Defaults fuer ALLES)
3. RichText (ueberschreibt Defaults pro Run)
Plus Font-Tabelle in _runs_to_rtf jetzt mit \fnil\fcharset0 Marker
(Rhinos RTF-Parser kann sonst font-Eintraege ignorieren und Default
verwenden).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Bug: Stile aendern nichts im Editor oder springen alle in eine
Zeile. Mit "Herumfummeln" partiell ge-fixed. Root-Causes:
1. Toolbar-Buttons stehlen Focus aus Editor → Selection futsch →
execCommand wirkt auf nichts. Fix: onMouseDown + preventDefault
auf B/I/U/Sup/Sub/Align (Pill akzeptiert jetzt onMouseDown prop).
2. Editor-div hat fontFamily/fontSize aus React-State → ueberschreibt
per-Span-Styles → alles sieht gleich aus. Fix: editor-div hat
statische Defaults (Helvetica 20px), per-Selection Styles wirken
ueber span-Wrapping (applyInlineStyleToSelection).
3. Newlines kollabieren (text springt auf eine Zeile). Fix:
white-space: pre-wrap auf editor-div.
4. Font/Size dropdowns: alter execCommand fontName war buggy. Neu:
applyInlineStyleToSelection('font-family', font) bzw. 'font-size'
wickelt die Selektion in ein <span style="..."> ein, neue Selection
liegt auf dem Span (Folge-Operationen wirken sauber).
5. Selection-change Event-Listener speichert die letzte Editor-Selection
in savedRangeRef. restoreSelection() vor jeder Operation stellt sie
wieder her — robust auch wenn der Focus zwischendurch weg war.
Backend (_runs_to_rtf): BASE_PX = base_size_m * 100 statt hardcoded 14.
Frontend rendert 1m = 100px, also entspricht base_size_m*100px dem
\\fs20 in RTF (= 1.0× TextEntity.TextHeight). _commit passes settings.
size mit, damit das Mapping stimmt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User: Doppelklick auf DOSSIER-Text oeffnet weiterhin Rhinos Editor.
Verschiedene Groessen im Editor erscheinen nicht in Rhino.
Doppelklick-Hook (rhino/text_editor.py):
- _DossierTextDoubleClickHook subklassiert Rhino.UI.MouseCallback.
OnMouseDoubleClick prueft selektierte TextEntities auf UserString
"dossier_text"="1" und cancelled das Event (= blockt Rhinos
Standard-TextEdit-Dialog), setzt sticky["dossier_pending_text_edit"]
mit der Obj-ID
- _on_idle_check_pending_edit (RhinoApp.Idle event): nimmt sticky-ID
auf naechstem Idle-Tick und ruft open_for_edit(obj) — defer noetig
weil Eto-Form aus MouseCallback heraus oeffnen Re-Entrancy macht
- _ensure_double_click_hook() installiert Hook + Idle-Handler einmalig
pro Rhino-Session (idempotent)
- startup.py ruft das jetzt direkt nach Modul-Load auf
Edit-Mode (open_for_edit):
- Liest aus bestehendem TextEntity die Settings (Font, Size, Bold,
Italic, Underline, Align) + PlainText
- Frame fuer Dialog-Positionierung aus BBox abgeleitet
- TextEditorBridge mit edit_obj_id + initial_text gestartet
- INIT-Payload um initialText + editMode erweitert
- COMMIT: bei edit_obj_id gesetzt → doc.Objects.Replace statt AddText.
Plane wird vom Original uebernommen wenn keine explizite Rotation,
damit der Text an seinem Platz bleibt
Frontend (TextEditorApp.jsx):
- Bei INIT mit initialText: editor.innerText wird damit befuellt
- htmlToRuns extrahiert font-size in Pixel pro Run (inline style oder
computed style != base)
- baseCtx _basePx aus computed style des Editor-divs
Size-Mapping (rhino/text_editor.py _runs_to_rtf):
- fontSizePx in Runs triggert non-trivial (RTF wird generiert)
- Pro Run: \fs in Halb-Punkten = 20 * (run_px / 14_base_px) round
- 14px = \fs20 (1.0× TextEntity.TextHeight)
- 21px = \fs30 (1.5×)
- 28px = \fs40 (2.0×)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Wunsch: vorgespeicherte Stile (Heading, Paragraph Helvetica/Georgia)
direkt im Editor anwendbar.
Backend (text_create.py):
- _DEFAULT_STYLES: 7 sinnvolle Architektur-Defaults — Titel (0.40m bold),
Heading 1 (0.30m bold), Heading 2 (0.20m bold), Paragraph Helvetica
(0.15m), Paragraph Georgia (0.15m Georgia), Notiz (0.10m italic),
Bildlegende (0.08m italic)
- list_styles: seedet die Defaults beim ersten Zugriff falls noch keine
Styles im Doc existieren (analog mass_style)
- Bestehende save_style/delete_style/apply_style funktionieren weiter
Backend (text_editor.py):
- INIT-Payload erweitert um styles[] (Liste aller verfuegbaren Stile
mit id/name/font/size/bold/italic/underline/align)
Frontend (TextEditorApp.jsx):
- Neuer Stil-Picker als erstes Dropdown in Toolbar-Row 1 (150px)
- Optionen: "— Stil wählen —" + alle verfuegbaren Stile
- onChange: applyStyle(style) — setzt Toolbar-State + appliziert via
execCommand auf die aktuelle Selektion im WYSIWYG-Editor (oder als
Default fuer kommendes Tippen wenn keine Selektion)
- queryCommandState-Check fuer Bold/Italic/Underline damit nur toggled
wird wenn nicht schon im gewuenschten State
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Wunsch: eigener WYSIWYG-Editor im React/Topbar-GUI-Stil. Topmost.
Verschiedene Schriftarten/-Dicken sichtbar im Editor selbst.
Neues Backend (rhino/text_editor.py):
- TextEditorBridge mit Frame-Daten im Konstruktor, INIT-Push mit
Settings + Font-Liste, COMMIT erstellt TextEntity, CANCEL schliesst
- open_with_frame(p1, p2, origin, width, height): oeffnet Satellite-
Window mit mode='text_editor' + topmost=True
- panel_base.open_satellite_window: neuer Parameter topmost (default
False) der form.Topmost setzt
text_create.create_text: ruft jetzt text_editor.open_with_frame nach
dem Frame-Pick. Eto-basierter _dossier_text_editor bleibt im Modul als
Fallback aber wird nicht mehr verwendet.
Neues Frontend (src/TextEditorApp.jsx, mode='text_editor'):
- Layout im DOSSIER-Topbar-Stil (dunkle Pills, accent on hover)
- Pill-Helper-Komponente fuer alle Toggle/Action-Buttons
- Dropdown-Helper fuer Font + Size
- Toolbar Row 1: Font-Dropdown | Size-Dropdown | Color-Picker | Layer-Reset
- Toolbar Row 2: B/I/U mit Material-Icons | L/C/R Align | x²/x₂ Sup/Sub
- Sonderzeichen-Palette: 41 Unicode-Symbole (Architektur/Math/Pfeile/
Auszeichnungen), Klick inserted am Cursor
- WYSIWYG-Editor: contentEditable div mit fontFamily=ausgewaehlt,
textAlign=ausgewaehlt — Format-Toolbar wirkt via document.execCommand
(bold/italic/underline/justifyLeft/Center/Right/superscript/subscript/
fontName/foreColor)
- "Einfuegen" sendet COMMIT mit text (innerText) + settings
- "Abbrechen" CANCEL → Bridge schliesst Form
Phase 1 Limitation: rendert in Rhino als PlainText mit den globalen
Settings (font/size/bold/italic/align/color) — verschiedene Schriftarten
INNERHALB des Texts sind im Editor sichtbar aber nicht im finalen
Rhino-TextEntity. Phase 2: HTML → Rhino RichText/RTF Mapping.
rhinoBridge.js: send() jetzt exportiert (war intern) damit
TextEditorApp generisch COMMIT/CANCEL senden kann.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. Dialog-Positionierung: vp.WorldToScreen existiert nicht auf dieser
Rhino-Version. Ersetzt durch vp.WorldToClient → System.Drawing.Point
(viewport-lokale Pixel) + view.ScreenRectangle fuer absolute Position.
→ Dialog poppt jetzt wirklich neben dem Frame statt random.
2. TextArea-Hoehe: DynamicLayout expandiert die TextArea nicht
zuverlaessig (zeigte sich als 1-Zeilen-Streifen mit Buttons riesig
daneben). Fix: ta.Size = drawing.Size(...) explizit setzen.
3. 5-arg Font(face, FontWeight, FontStyle, underline, strike): Python.NET
3.0 erlaubt keinen bool→Enum-Cast mehr (Log: "int can not be converted
to Enum implicitly"). Fix: echte Enums Rhino.DocObjects.Font.FontWeight.
Bold/Normal + FontStyle.Italic/Upright benutzen. Damit funktioniert
auch Underline-Support endlich.
apply_settings_to_selection: kompletter Rewrite — statt Duplicate-Modify
wird eine FRESH TextEntity gebaut + alle Properties (Plane, PlainText,
TextHeight, Font, Align) gesetzt + per Replace eingebunden. DimStyle
wird auf Guid.Empty entkoppelt damit nicht die Style das Font-Setting
ueberschreibt. Sollte Bold/Italic-Un-Toggle-Bug fixen.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User: Frame wird gezeichnet aber Editor erscheint nicht — kann nicht
schreiben. Eto.Form mit WindowStyle.None + Form.Show() funktioniert auf
Mac WebKit/Rhino-Build nicht zuverlaessig (kein Render oder hinter Rhino-
Window).
Fix: Eto.Dialog mit ShowModal — laeuft proven auf Mac+Windows. Dialog
hat normale Chrome (Title-Bar, OK/Cancel-Buttons) aber wird neben dem
gepickten Frame positioniert (via vp.WorldToScreen + view.ScreenRectangle
→ Dialog.Location 20px rechts vom Frame). Tradeoff zu "inline im
Viewport": Dialog hat Rahmen, aber ist sichtbar und funktional.
Workflow: pick Frame → Dialog poppt neben Frame mit TextArea + OK/Cancel
+ Cmd/Ctrl+Enter Shortcut + Esc-Abbruch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Wunsch:
- Rechteck als Frame statt 2-Punkte-Linie picken
- Direkt im Viewport schreiben statt zentrierter Dialog
- Bold/Italic/Underline echt anwendbar
Aenderungen:
_pick_text_frame: DynamicDraw-Event auf GetPoint zeichnet Live-
Rechteck-Vorschau in petrol-accent waehrend User die 2. Ecke picked
(analog Rhinos _Rectangle). Returns jetzt (p1, p2, origin, w, h).
_inline_editor (neu, ersetzt _frame_editor_dialog): chromeloses
Eto.Form (WindowStyle.None_) absolut positioniert ueber dem gepickten
Frame im Viewport via vp.WorldToScreen + view.ScreenRectangle. TextArea
fuellt den Frame. Cmd+Enter / Ctrl+Enter = commit, Esc = abbrechen.
Look-and-feel: schreibst direkt "im" Feld auf der Arbeitsflaeche.
_apply_font: erweitert um 5-arg Font(face,bold,italic,underline,strike)
Konstruktor als Pfad 1 (falls Rhino-Version das unterstuetzt). Fallback
3-arg Font, FromQuartetProperties, FindOrCreate.
apply_settings_to_selection: vor jedem Font-Set ein PlainText-Reset
damit eventuelle Rich-Text-Formatierungs-Runs nicht das neue te.Font
ueberschreiben — vermutlicher Bold-Toggle-Bug-Fix. Underline jetzt im
patch-Loop mit drin.
read_selection_settings: liest auch font.Underlined wenn vorhanden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User: Bold/Kursiv liessen sich nicht zurueck auf normal stellen.
Diagnose: te.Font.QuartetName kann je nach RhinoCommon-Version den Bold/
Italic-Suffix im Namen mitfuehren (z.B. "Helvetica-Bold"). Dann liest
read_selection_settings face="Helvetica-Bold" → wird in updateTs ans
Backend zurueckgeschickt → _apply_font ruft FindOrCreate("Helvetica-Bold",
False, False) → das matcht intern wieder die Bold-Variante = bleibt fett.
Fix in _apply_font:
- Suffix-Stripping: -Bold, -Italic, -Oblique, -BoldItalic etc. werden vom
face-String entfernt damit nur die Base-Family ("Helvetica") bleibt
- FromQuartetProperties zuerst (konstruiert Font direkt, unabhaengig vom
FontTable-Cache). FindOrCreate als Fallback.
- Diagnostic print: "[TEXT] _apply_font face=... bold=... italic=..."
damit sich nachvollziehen laesst was tatsaechlich angewendet wird
Plus textSettings/textStyles im State-Sig hinzugefuegt damit Idle-Pushes
Aenderungen nicht dedupelt verschlucken.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Wunsch: Text-Editor war unfertig — keine Fonts sichtbar, Bold liess
sich nicht entfernen, Size soll Dropdown mit Eigene, Text-Stile noetig,
Unterstrichen + Links/Mitte/Rechts fehlten, schoenere Icons.
Backend (text_create.py):
- DEFAULTS erweitert um underline + align (left/center/right)
- _normalize() validiert Settings (align nur left/center/right)
- Text-Style-Preset-System analog mass_style:
- list_styles / save_style / delete_style / apply_style
- get_active_style_id / set_active_style_id
- doc.Strings["dossier_text_styles"] (JSON list mit id/name + settings)
- doc.Strings["dossier_text_style_active"]
- _apply_align(te, "left"|"center"|"right") setzt TextHorizontalAlignment
- apply_settings_to_selection + create_text rufen _apply_align mit auf
- read_selection_settings liest auch align
- available_fonts mit Fallback-Liste (Helvetica, Arial, Times, etc.) wenn
Rhino.DocObjects.Font.AvailableFontFaceNames leer ist
- underline: in Settings + Styles persistiert, NOCH NICHT visuell
appliziert (braucht TextEntity-RichText-API)
Backend (oberleiste.py):
- Neue Handler APPLY_TEXT_STYLE / SAVE_TEXT_STYLE / DELETE_TEXT_STYLE
- State liefert textStyles + textStyleActiveId
- textFonts jetzt bei jedem _send_state mitgeschickt (vorher one-shot mit
_fonts_sent flag — verlor sich nach Panel-Re-Mount und User sah keine
Fonts mehr)
Frontend (OberleisteApp):
- Text-Block komplett neu gelayoutet (3 Spalten Grid):
Reihe 1: [Style ▼] [Font ▼] [Size ▼]
Reihe 2: [B|I|U] [L|C|R] [+]
- Style-Dropdown mit Optionen "+ Speichern…" und "🗑 Aktiven loeschen"
- Size-Dropdown mit Preset-Werten (0.05/0.10/.../1.00 m) + "Eigene…"
→ toggle zu Custom-Number-Input bei "Eigene"-Auswahl
- B/I/U mit Material-Icons format_bold/italic/underlined statt B/I-Text
- L/C/R Alignment-Buttons mit format_align_left/center/right
- ToggleBtn-Helper-Komponente fuer alle 6 Toggles
- "+" Insert-Button bleibt klein (Icon size 14)
- Accent-Border auf allen Pills wenn Text selektiert (visuelles Feedback
"Aenderungen wirken auf Selektion")
- Bold/Italic/Underline lassen sich jetzt sauber togglen (waren als
proper Booleans serialisiert — vorher Bug evtl. durch fehlende Font-
Liste maskiert)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Probleme:
1. Floating Eto-Dialog erschien nicht nach GetPoint (Mac-Bug)
2. UI war zu klobig, sollte vectorworks-mässig kompakt sein, "+" als
kleines Icon-Symbol
3. Selektierten Text aendern war nicht moeglich
Fix 1 — Bug: _floating_input geloescht, ersetzt durch
_prompt_for_text() das Rhino.UI.Dialogs.ShowEditBox benutzt. Nativer
cross-platform Dialog ohne Eto-Modal-Bug. Workflow:
GetPoint → ShowEditBox → AddText. Funktioniert auf Mac.
Fix 2 — UI: Text-Block kompakt umgebaut.
Reihe 1: [Font ▼] | [Size m] (130 + 60 = 196px)
Reihe 2: [B][I][+] segmented pill (gleiche Breite wie Reihe 1)
"+" ist jetzt kleines add-icon (size 13), kein "+ Text" Label mehr.
Fix 3 — Edit-Selection: neue Funktionen in text_create.py:
- _selected_text_objects(doc) → Liste der selektierten TextEntities
- read_selection_settings(doc) → Settings der ersten Selektion
- apply_settings_to_selection(doc, patch) → wendet font/size/bold/italic
auf alle selektierten TextEntities an
oberleiste.SET_TEXT_SETTINGS handler appliziert die Aenderung jetzt
ZUSAETZLICH auf die Selection (wenn vorhanden) — UND speichert als
Default. State enthaelt textSelectionSettings, UI nutzt diese als
Anzeige-Werte wenn vorhanden (Werte spiegeln Selektion live).
Visual: Border-Color der Size/Segmented-Pill wird accent wenn Selection
aktiv ist (Hinweis dass Aenderungen auf Selektion wirken).
Sig-Update: textSelectionSettings + lastSetView in last_state_sig damit
State neu gepusht wird wenn sich Selection/View aendert.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Feedback:
1. View-Bars sind hoeher als andere Elemente auf der Seite
2. Active-Highlight bleibt auf Top haengen — andere Views leuchten nicht
3. Glitch: Klick auf Top → Bar zeigt weiterhin Perspektive aktiv
Fix 1 (Hoehe): Stat-Box Inhalts-Hoehe BAR_H*2+4 → BAR_H*2+6, der innere
Trennstrich-Gap 4 → 6. Damit visual 50 → 52 = identisch mit den 2-row-
Blocks (View, Preset, Massstab).
Fix 2 + 3 (Active-Highlight): Backend trackt `self._last_set_view` ←
gesetzt wenn handler in SET_VIEW erfolgreich war. Frontend matchView
prueft zuerst `state.lastSetView === v` — kein Race-Condition zwischen
ChangeProjection und Viewport-State-Lesen mehr.
Fallback auf Viewport-State-Detection wenn lastSetView noch null
(initial load). N/O/S/W kriegen jetzt auch Active-Highlight (vorher
hartcoded false).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Feedback: About sollte nicht als overlay im Panel erscheinen sondern
ein echtes OS-Fenster sein wie Kamera/Masse-Settings.
Neu:
- rhino/about.py: open_as_window() via panel_base.open_satellite_window
(read-only, kein Bridge-Save/Cancel-Callback noetig)
- src/AboutApp.jsx: gleiche Inhalte wie der vorige Modal — Versionen,
Autor, Website, Lizenz — in einer 440x380 Eto-Form
- src/main.jsx: mode 'about' → AboutApp
- openAbout() in rhinoBridge.js sendet OPEN_ABOUT an Oberleiste
- OberleisteBridge handler OPEN_ABOUT → about.open_as_window()
OberleisteApp:
- Logo-onClick aufgeräumt: openAbout() statt setAboutOpen(true)
- aboutOpen-State und die AboutModal-Komponente entfernt
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>