Commit Graph

88 Commits

Author SHA1 Message Date
karim 51987dcc38 Text-Editor: Selection-Preservation + per-Span Font/Size
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>
2026-05-21 01:54:25 +02:00
karim f4404db64a Doppelklick-Hook auf DOSSIER-Texte + Size-Mapping in RTF
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>
2026-05-21 01:44:45 +02:00
karim e7a1753519 Text-Editor: Default-Stile + Stil-Picker im Dialog
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>
2026-05-21 01:38:23 +02:00
karim 54aa1c9e84 Text-Editor: Symbol-Popover, Frame/Mask/Rot/Kamera, Phase 2 RTF-Mapping
User: Sonderzeichen in einen Button packen mit Popover-Box, neue
Optionen (Frame around text, Horizontal to view, Rotation, Mask
margins), und Phase 2 = mixed-fonts via Rich-Text.

Frontend (TextEditorApp.jsx):
- Sonderzeichen-Reihe weg, ersetzt durch [Symbole] Pill-Button mit
  Popover-Box. Symbols gruppiert (Mathematik, Pfeile, Auszeichnung)
- Toolbar Reihe 3 neu: [Rahmen ▼] [Mask ____ m] [Rot ____ °]
  [Zur Kamera Toggle] [Symbole Popover]
- htmlToRuns(rootEl): walks contentEditable DOM, extrahiert Format-
  Runs (text, font, color, bold, italic, underline, sup, sub) basierend
  auf Tag-Hierarchie (b/i/u/sup/sub/font/span style)
- onCommit sendet jetzt zusaetzlich runs[] + frame/horizontalToView/
  rotation/maskMargin in settings

Backend (text_editor.py):
- _commit: setzt Plane mit Rotation um Z (math.radians + Plane.Rotate)
- MaskFrame: NoFrame/RectFrame/CapsuleFrame ueber TextMaskFrame-Enum
- MaskEnabled+MaskOffset+MaskUsesViewportColor wenn maskMargin>0
- te.DrawForward = horizontalToView (Text steht zur Kamera)
- _runs_to_rtf(runs, default_font): Phase 2 — generiert Rhinos RTF aus
  den Runs. Triviale Runs (alle plain, ein Font) → None (PlainText
  fallback). Sonst: \rtf1 + fonttbl + colortbl + Format-Codes pro Run
  (\f \cf \b \i \ul \super \sub \nosupersub). _rtf_escape handelt \,
  {, }, \n und non-ASCII (\u-Notation). te.RichText = rtf, Fallback
  auf PlainText wenn das fehlschlaegt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:35:28 +02:00
karim ab0ecfbf14 React WYSIWYG Text-Editor (Topmost Satellite-WebView) — Phase 1
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>
2026-05-21 01:28:26 +02:00
karim b047d0aa4b DOSSIER Custom Text-Editor + 3 Eto-Bugfixes + Tagging
User-Wunsch: eigener Editor-Dialog mit Toolbar (Font, Size, Farbe,
B/I/U/Align), Sonderzeichen-Palette, Sub/Superscript. Eigenes "Symbol"
(= unser Topbar-+-Button) triggert ihn.

Neuer _dossier_text_editor(p1, p2, settings, fonts):
- Eto.Dialog "Dossier Text", positioniert neben Frame
- Row 1 (Format): Font-Dropdown | Größe (NumericStepper) | ColorPicker
  + Layer-Reset-Button
- Row 2 (Style): B / I / U Checkboxes + L/C/R Align-RadioButtons +
  x² (Hochstellen) / x₂ (Tiefstellen) — konvertieren markierte Ziffern
  via Unicode-Map (⁰¹²³... bzw. ₀₁₂...)
- Symbol-Palette: 42 Sonderzeichen in 12er-Reihen, Click inserted am
  Cursor: ∅ Ø ⌀ ° ± × ÷ ² ³ ½ ¼ ¾ ⅓ ⅔ ≤ ≥ ≠ ≈ ∞ √ ∆ π µ ← → ↑ ↓ ↔ ↕
  • · ▪ ◆ ★ ☆ ✓ ✗ § ¶ © ® ™
- Multi-Line TextArea (560×240, wrap, AcceptsReturn)
- Cmd/Ctrl+Enter = OK, Esc = Cancel
- Returns dict {text, font, size, bold, italic, underline, align, color}

create_text rewired: ruft _dossier_text_editor statt _inline_editor.
- Save settings (ohne color) als neue Defaults
- TextEntity mit allen Properties + Wrap im Frame
- ObjectAttributes.SetUserString("dossier_text", "1") fuer spaeteren
  Double-Click-Hook (Phase 2: Doppelklick auf getaggten Text re-oeffnet
  unseren Editor statt Rhinos Standard)
- Farbe als ColorFromObject wenn explizit gesetzt, sonst Layer-Farbe

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:18:01 +02:00
karim cc0e6d814e Text-Editor 3 echte Bugs gefixt (aus User-Log)
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>
2026-05-21 01:13:05 +02:00
karim 9eece87cc4 Text-Editor zurueck auf Dialog.ShowModal (chromeloses Form ging nicht)
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>
2026-05-21 01:06:13 +02:00
karim 9256e5866e Text: Rechteck-Preview + Inline-Viewport-Editor + Underline-Versuch
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>
2026-05-21 01:03:25 +02:00
karim e9f0e255a0 Text-Create: InDesign-Stil Frame + Multi-Line-Editor (Phase 1)
User-Wunsch: wie InDesign — Text-Feld aufziehen und reinschreiben statt
Single-Point + ein-Zeilen-Prompt.

Phase 1 — Frame + Multi-Line-Eingabe:
- _pick_text_frame(): User picked 2 Ecken (mit Live-Vorschaulinie ab
  Erstpunkt), liefert (origin, width, height)
- _frame_editor_dialog(initial): Eto.Dialog mit forms.TextArea
  (multi-line, wrap, AcceptsReturn) + OK/Abbrechen-Buttons.
  Default = OK-Button (Enter im TextArea aber legt Newline → OK
  per Maus-Klick oder DefaultButton-Enter-Sequenz)
- create_text(): pickt Frame → oeffnet Editor → erstellt TextEntity an
  origin (= obere linke Ecke), Plane mit Z-Axis, Text-Wrap auf Frame-
  Breite (best-effort: FormatWidth/TextWidth/MaskWidth/TextIsWrapped
  je nach RhinoCommon-Version vorhanden)

Phase 2 (noch nicht): verschiedene Fonts INNERHALB eines Texts (Rich-
Text-Runs). Braucht Mapping Eto.RichTextArea → Rhino's RTF-Format.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:55:10 +02:00
karim 9f257b83e6 Text-Bold/Italic-Toggle: face-Suffix-Stripping + FromQuartet zuerst
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>
2026-05-21 00:51:13 +02:00
karim 38041ab6a0 Text-Editor: Stile, Size-Dropdown, U+Align, bessere Icons, Fonts-Fallback
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>
2026-05-21 00:44:17 +02:00
karim 6fce00343c Text-Block: Bug-Fix + Vectorworks-Style + Edit-Selected
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>
2026-05-21 00:32:16 +02:00
karim 38314bcc6f Topbar padding-top 3 → 4 (≈ +30%)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:26:16 +02:00
karim 79c8392c2c Pill-Hoehen erzwingen: box-sizing border-box + min/max-height auf Buttons
User: View-Bars und Massstab-Buttons-Pill sind hoeher als die Dropdowns
trotz gleicher BAR_H. Browser-Defaults fuer <button> koennen trotz
padding:0 implizite Mindesthoehen einfuehren — besonders auf macOS WebKit.

Fix:
- Alle Outer-Pills (View Row1/Row2, Massstab Buttons-Pill, BarCombo)
  bekommen explizite height = BAR_H+2 + box-sizing: border-box. Damit
  visual genau BAR_H+2 = 24px egal welche default-render-Schicht
  drueberkommt.
- Inner Buttons (cellStyle + SegBtn): minHeight/maxHeight = BAR_H,
  appearance: none, line-height: 1, box-sizing border-box. Lockt die
  Hoehe absolut.
- Massstab SegBtn Icon-Size 13 → 11 (analog View-Icons)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:23:04 +02:00
karim c16f5ea740 View-Toggle: Icons 13→11, N/O/S/W Font 11→10, BarSelect Dead-Code raus
User: View-Bars wirken hoeher als andere Bars. Hoehen sind mathematisch
identisch (BAR_H + Border = 24 visual pro Reihe), aber 8 Buttons mit
Border-Trennlinien wirken visuell dichter als ein einzelner Dropdown.

Visual-Fix per User-Vorschlag: Icons + Font in Views minimal verkleinert,
macht die Bars weniger "dominant".

Plus BarSelect (komplett unused Dead-Code seit Migration zu BarCombo)
geloescht — der hatte als einziger noch bg-item statt bg-input und
verwirrte die Background-Konsistenz-Frage.

Hintergrund-Check: Alle Pills nutzen jetzt einheitlich var(--bg-input),
Hover var(--bg-item-hover), Active var(--accent).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:17:37 +02:00
karim 872832a3cc View-Toggle: Active aus lastSetView + Stat-Box-Hoehe angeglichen
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>
2026-05-21 00:13:08 +02:00
karim 4a31652f78 Stat-Box: gruener Border wenn at-scale (analog Hover-Akzent)
User: wenn die 2-stoeckige Massstab-Pille gruen ist (atScale=true), soll
auch der Border gruen sein — konsistent mit den anderen Hover/Active-
Akzenten in der Topbar.

Border: '1px solid border' → '1px solid (accent wenn atScale, sonst
border)'. Transition fuer smooth Wechsel beim Snap auf 100%.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:06:20 +02:00
karim 205c626a5a View-Toggle 2x4: TOP/ISO/PERSP/Cam + N/O/S/W mit Norden-Rotation
User-Vorschlag: Architektur-konformes View-Layout — 3D-Views oben, 4
Gebaeudeansichten unten. Plus Norden-Rotation als Doc-Setting damit bei
rotierten Projekten (swissBUILDINGS, Sonnenberechnungen) die richtigen
Wandansichten gepickt werden.

Backend (rhino/kamera.py):
- get_north_angle/set_north_angle — doc.Strings["dossier_north_angle"]
  (Grad im Uhrzeigersinn von +Y, default 0°)
- _scene_target_and_diag(doc) — gemeinsamer Helper fuer Szenen-Center +
  Diagonal-Distanz
- set_cardinal_view(vp, 'N'|'O'|'S'|'W'): rotiert Kamera-Position via
  Norden-Vektor. Parallel-Projektion, Camera-Z = Target-Z (echte
  Elevation), Up-Vektor +Z.
- set_top_view(vp): Plan-Ansicht mit Norden = Up-Vektor (Plan rotiert
  visuell wenn Norden != +Y)
- _set_iso(vp, octant): Octant-Richtung jetzt aus north+east-Vektoren
  konstruiert → ISO rotiert mit Norden mit
- Bridge-Handler SET_NORTH_ANGLE + state.northAngle, notify Oberleiste

Backend (oberleiste.py):
- SET_VIEW erweitert: Top → kamera.set_top_view, N/O/S/W →
  kamera.set_cardinal_view, Iso → kamera._set_iso. Front/Right/etc bleibt
  als Legacy direkt-Rhino-Call.
- State liefert northAngle

Frontend (OberleisteApp):
- VIEWS_ROW1: TOP/ISO/PERSP + Kamera-Settings-Button (Icons only)
- VIEWS_ROW2: N/O/S/W als DM-Mono-Buchstaben
- 2x4-Grid, VIEW_W=140 (konsistent mit Massstab-Pills), CELL_W=35
- matchView nur fuer Top/Iso/Perspective; Cardinals haben keinen
  Active-State (Viewport-Name ist nicht zuverlaessig erkennbar)

Frontend (KameraApp):
- Plan-Norden Section mit Number-Input (Grad, 0.5°-Step) + Reset-Button
- Hinweis-Text dass Wirkung auf TOP/ISO/N/O/S/W geht

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:04:44 +02:00
karim 2252ffd2f9 Text-Erstellung mit Floating-Input-Box (Variante B)
Neuer Workflow: Klick "+ Text" in Topbar → Punkt im Viewport picken →
Floating Eto-Dialog erscheint neben dem Mauszeiger → User tippt → Enter
fuegt TextEntity mit Topbar-Settings ein. Esc bricht ab.

Backend (rhino/text_create.py):
- load_settings/save_settings — persistiert font/size/bold/italic in
  doc.Strings["dossier_text_settings"] (JSON)
- available_fonts() — System-Font-Namen via
  Rhino.DocObjects.Font.AvailableFontFaceNames
- _floating_input() — Eto.Dialog mit TextBox, ShowModal mit Rhino-
  MainWindow als Parent, positioniert bei Mouse.Position
- create_text() — RhinoGet.GetPoint → _floating_input → TextEntity
  mit Font/Size/Bold/Italic erstellen + AddText
- _apply_font() mit 2 Fallback-Pfaden (FontTable.FindOrCreate +
  Font.FromQuartetProperties) fuer RhinoCommon-Kompatibilitaet

Backend (oberleiste.py):
- CREATE_TEXT handler → text_create.create_text()
- SET_TEXT_SETTINGS handler → text_create.save_settings (merge partial)
- State payload: textSettings (immer) + textFonts (einmalig initial,
  via _fonts_sent Flag — Liste aendert sich nicht zur Laufzeit)

Frontend (OberleisteApp + rhinoBridge):
- createText() + setTextSettings() Bridge-Funktionen
- Text-Block 2x2 Grid analog Massstab:
  R1: Font-Dropdown (BarCombo mit text_fields icon) | Size-Input mit "m" suffix
  R2: B/I-Toggles (segmented pill mit accent-Fill bei active) | "+ Text" Button
- Hover-Logik analog View-Toggle (bg → bg-item-hover, color → accent-light)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:52:00 +02:00
karim 817455bbba Massstab Stat-Box: Live + Zoom-% als EINE gerundete Pill statt zwei
User: aus den zwei uebereinander stehenden Chips (1:405 / 4.9%) eine
einzelne gerundete Pill machen. Text-Hoehen bleiben wie vorher, dazwischen
ein duenner Trennstrich.

Aufbau:
- Outer: grid-row spans both Reihen, width STAT_W, height BAR_H*2+4
- Innen: top BAR_H mit live-Text, 4px Gap mit zentriertem 1px-Trennstrich
  (left/right 6px Inset), bottom BAR_H mit Ratio-Text
- Rundung: borderRadius 14 (gerundete Ecken)
- atScale: accent-dim bg + accent-light text fuer beide Texte

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:46:00 +02:00
karim e2c13e7844 Massstab: Pills gleich breit + Icon weg + weisse Texte + Hover-Akzent
User-Wunsch:
- Aktueller Massstab (live) + Zoom-% weiss, accent bei 100%
- Dropdown immer weiss (kein valueAccent mehr)
- Buttons unten wie View-Toggle (weiss default, gruen on hover)
- Massstabs-Icon weg, Pills (Dropdown + Buttons-Bar) gleich breit + exakt
  uebereinander

Aenderungen:
- BarCombo: Icon-Slot nur wenn `icon` truthy gerendert (kein leerer Slot)
- Massstab-Dropdown ohne icon-Prop → kein 18px Slot links, Pill sitzt
  direkt in der Grid-Zelle
- PILL_W=140 für Dropdown UND Buttons-Pill, jeder Button BTN_W=35
- statChipStyle: color text-primary (weiss) statt text-muted, accent-light
  nur bei atScale=true
- valueAccent prop entfernt aus Massstab-Dropdown
- SegBtn mit Hover-Logik analog View-Toggle: bg → bg-item-hover,
  color → accent-light, active bleibt accent-fill

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:40:45 +02:00
karim 3f5f48cb2c Caret differentiated + Accent-Hover wie in Elemente
User:
1. Wireframe-Caret war fast draussen (zu weit rechts), bei anderen
   minimal mehr rechts erwartet
2. Hover-Effekt mit accent-border wie in Elemente fuer alle Pills

Fix 1: backgroundPosition differenziert:
- Ohne Gear: 'right 10px center' (normaler Pill-Rand-Abstand)
- Mit Gear: 'right 1px center' (klebt am Gear)
So sitzt der Caret optisch konsistent — bei Display innen, bei den
anderen direkt vor dem Gear.

Fix 2: Hover-Akzent analog ElementeApp PillButton:
- BarCombo Pill-Container: bg → bg-item-hover, border → accent
- BarButton (Camera, Settings-Gears in Pills, Print-Toggle): selbe Logik,
  active-State bleibt accent-fill
- View-Toggle-Buttons: bg → bg-item-hover, text → accent-light, active
  bleibt accent-fill
Transitions 0.15s fuer smooth feel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:32:24 +02:00
karim b0badbbda6 BarCombo Caret: right 9px → 2px, klebt jetzt am Gear
User: Caret-Abstand zum Gear soll symmetrisch zum Abstand vom Gear zum
rechten Pill-Rand sein. Vorher saß der Caret 9px links vom Gear, der Gear
8px vom rechten Pill-Rand → asymmetrisch (caret-to-gear groesser als
gear-to-edge).

Fix: backgroundPosition 'right 2px center'. Caret-Image rechtes Ende 2px
vom Select-Rand, Caret-Center ~7px links vom Gear-Button-Linker-Rand,
plus 8px Gear-Padding-Left + 6px halbes Gear-Icon = ~13px caret-to-gear-
icon. Gear-to-edge = 8+6 = 14px. Symmetrisch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:24:07 +02:00
karim ea8292ed14 Massstab 2x2-Grid: Live | Dropdown / Ratio | Buttons
User-Korrektur: Layout sollte 2x2 sein:
  Reihe 1: [Aktueller Massstab]  [Dropdown gesetzter Massstab]
  Reihe 2: [Zoom-Verhaeltnis %]   [Buttons]

Restrukturiert auf CSS-Grid mit 2 Spalten, 2 Reihen.
- Live + Ratio-Chips links (80px fixe Breite, monospace)
- Dropdown + Buttons rechts
- Stat-Chip-Helper statChipStyle(accentTint): accentTint=true bei 100% match

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:22:12 +02:00
karim ea4c891b98 Massstab: Dropdown (gruen) oben, Zoom-Verhaeltnis unten
User-Wunsch: ueber dem aktuellen Live-Zoom soll der gesetzte Massstab in
gruen oben sein, unten das Verhaeltnis des aktuellen Zooms zum gesetzten.

Layout:
- Buttons-Pill links (vertikal zentriert ueber beide Reihen)
- Rechts oben: Massstab-Dropdown — BarCombo mit neuem valueAccent prop,
  faerbt Select-Text in accent-light + bold wenn ein Massstab gesetzt ist
- Rechts unten: Zoom-Ratio-Chip (% des gesetzten Massstabs).
  100% = at-scale → accent-dim Hintergrund + accent-light Text.
  <100% = rausgezoomt, >100% = reingezoomt → bg-input + muted text.

Ratio-Formel: appliedScale / liveScale * 100.
- 1:50 gesetzt, live 1:50 → 100% (exakt, accent)
- 1:50 gesetzt, live 1:100 → 50% (rausgezoomt)
- 1:50 gesetzt, live 1:25 → 200% (reingezoomt)

Per Klick auf das "%" Button (Reihe 1) snappt der Zoom auf 100%.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:18:31 +02:00
karim 0182497963 Massstab Row 1: 4 Buttons in segmented Pill statt Einzel-Knoepfen
User: Massstab-Block sah nicht huebsch aus, die 4 runden Buttons saßen
detached nebeneinander. Polish: alle 4 in einer durchgehenden Pill-Gruppe
(analog View-Toggle), bg-input fuer inaktive, accent fuer aktiven Print-
View-Toggle. Sieht jetzt visuell geschlossen aus.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:10:55 +02:00
karim 02b5fbfde4 BarCombo Caret: immer rechts vom Select (statt 30px reingerueckt bei Gear)
User-Screenshot: Caret stand bei Combos mit Gear irgendwo in der Mitte
zwischen Text und Gear. Grund: backgroundPosition war auf 'right 30px'
gesetzt damit der Caret nicht ueberlappt, aber er sass relativ zum Select-
Element (nicht zum Pill), also tatsaechlich weit links.

Fix: 'right 9px center' immer — der Select hat 22px right-padding und
endet direkt links vom Gear, der Caret klebt jetzt am rechten Rand des
Select-Bereichs (= so weit rechts wie moeglich ohne Gear zu ueberlappen).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:04:16 +02:00
karim b78a95caaa Massstab in 2 Reihen + View-Toggle inaktiv ebenfalls dunkel
User-Wunsch: Massstab-Stack analog Preset-Block. Plus inaktive View-
Toggle-Buttons sollen denselben dunklen Hintergrund haben wie der
Preset-Block.

Massstab:
- Reihe 1 (oben): Buttons (%, fit_screen, center_focus_strong, print/edit)
- Reihe 2 (unten): Live-Zoom-Chip + Massstab-Dropdown (BarCombo statt
  BarSelect — dunklerer bg-input + zusammenhaengender Pill)
- Custom-Input ebenfalls auf bg-input

View-Toggle: inaktive Buttons bg-item → bg-input (konsistent dunkel mit
allen anderen Pills). Aktiv bleibt accent-fill.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:03:02 +02:00
karim 1d1cd10a0b Oberleiste 2-Reihen-Block: Display|Kombi oben, Overrides|Masse unten
User-Wunsch: Overrides unter Wireframe, Kombi neben Wireframe, gleiche
Pill-Breiten + gleiche X-Achse, Boxen zusammenhaengend (kein Split
zwischen Select und Gear), dunkler wie Elemente.

Neue Komponente BarCombo:
- Icon roh links (18px fixe Breite → X-Alignment zwischen Reihen)
- iconClickable=true macht Icon zum Toggle-Button (fuer Overrides AN/AUS)
- Combined pill: ein gemeinsamer Container (bg-input — dunkler statt
  bg-item) mit select + optional gear in einem nahtlosen Rahmen
- Gear sitzt im selben Pill, kein border-left, transparent bg
- Caret-Position verschoben (right 30px) wenn gear vorhanden — Caret
  bleibt innerhalb des sichtbaren Select-Bereichs

Layout:
- CSS Grid 2x2 mit fester Pill-Breite (PRESET_W = 150)
- Reihe 1: Display | Kombi
- Reihe 2: Overrides | Masse
- Gleicher Spalten-Track in beiden Reihen → identische X-Positionen

Entfernt: BarSelect/BarButton im Display/Masse/Overrides/Kombi-Pfad,
alte Sektionen am Ende der Toolbar. BarButton bleibt fuer Camera + Zoom-
Buttons + Print/Edit. View-Toggle bleibt segmented-pill am Anfang.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:54:43 +02:00
karim 700cc11956 Oberleiste-Mittelweg: BAR_H 22, Font zurueck 11, Logo 17 — Toolbar-Gap bleibt
User: vorher zu klein. Mittelding zwischen 24 (original) und 20 (zu klein):
- BAR_H 20 → 22, PILL_H 18 → 20, Sep 18 → 20
- Select-Font 10 → 11, Padding 22/10 → 24/12, Caret 8 → 9
- BarButton Icon 12 → 13
- View-Toggle Font 9 → 10, Icon 12 → 13, Padding 8 → 10, Gap 3 → 4
- Live-Zoom Font 10 → 11, MinWidth 56 → 60
- Custom-Input Width 90 → 100, Padding 10 → 12, Font 10 → 11
- Logo 15 → 17

Toolbar-Gap 6 + Padding 3/10/6 bleiben (User: "find ich gut").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:39:58 +02:00
karim 66d6e63959 Oberleiste etwas kleiner — BAR_H 24→20, Fonts 11→10, Logo 18→15
User-Wunsch: alles etwas kleiner. Konsistent runter:
- BAR_H 24 → 20 (Selects, Buttons, View-Toggle, Live-Zoom, Massstab-Input)
- PILL_H 20 → 18 (Legacy-Pills)
- Sep 22 → 18 hoch, 4 → 3 Margin
- Toolbar-Gap 8 → 6, Padding 4/12/8 → 3/10/6
- Logo "DOSSIER." 18 → 15
- Select-Font 11 → 10, Padding 26/12 → 22/10, Caret-Pos 10 → 8
- View-Toggle: Font 10 → 9, Icon 13 → 12, Padding 10 → 8
- BarButton Icon 13 → 12
- Live-Zoom Font 11 → 10, MinWidth 64 → 56, Padding 12 → 10
- Custom-Massstab-Input: Width 100 → 90, Font 11 → 10

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:36:06 +02:00
karim cf40c03602 Zurueck zum Vectorworks-Look — Custom Pill-Chrome statt raw native HTML
User-Klaerung: erwartet wird der polierte Vectorworks-Style, nicht rohe
macOS-HTML-Form-Elemente. Vectorworks ist custom-gestylt um nativ
auszusehen, aber mit konsistentem Toolbar-Polish.

Zurueckgerollt:
- BarSelect: Pill-Container (bg-item, border, border-radius 999) +
  Custom-SVG-Caret aus --select-arrow. appearance:none. Icon roh links
  (Userpraferenz). joinedRight macht rechte Pill-Kante flach.
- BarButton: Pill-Container mit border + border-radius 999. active=true
  setzt accent-Background + bg-panel-Iconfarbe (klare Toggle-Anzeige).
  joinedLeft macht linke Kante flach fuer Verkettung.
- View-Toggle (Top/Front/Right/Iso/Persp): Segmented-Pill-Gruppe analog
  Vectorworks. Innere Kanten ohne Border, Aussenkanten gerundet.
  Active-Button: accent-fill + Bold.
- Custom-Massstab-Input: Pill-Chrome zurueck.

.native-control CSS-Klasse bleibt definiert (zukuenftige Verwendung), wird
aber in der Oberleiste nicht mehr referenziert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:28:17 +02:00
karim e19bbafe38 .native-control auch fuer Buttons — View-Toggle + BarButton system-nativ
User-Screenshot: dropdowns waren native, aber View-Toggle (Top/Front/...)
und die Icon-Buttons (Camera/Gear/Zoom/etc.) sahen weiterhin custom aus.

Grund: globales `button { border: none; background: none; }` strippt das
System-Push-Button-Styling fuer alle <button>s. `.native-control` deckte
nur select + input ab.

Fix:
- `.native-control` erweitert auf `button.native-control`: revert auf
  alle Button-Reset-Properties → WebKit rendert macOS-Push-Button-Chrome
- `.is-active`-Modifier: bold + accent-Tint fuer Toggle-Active-State
  (macOS-Native hat keinen "pressed-Toggle"-Look fuer normale Buttons)
- BarButton: className="native-control" + active=true → is-active
- View-Toggle (Top/Front/Right/Iso/Persp): pill-Container weg,
  individuelle native buttons mit is-active wenn matchView() true

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:18:37 +02:00
karim 8ad9e23838 .native-control Opt-out: global select-Style ueberschrieb das System-Native
User-Screenshot: dropdowns sehen immer noch wie pills aus. Grund: das
globale `select { appearance: none; background-image: var(--select-arrow);
border-radius: 999px; ... }` in index.css uebermalte die Inline-Resets von
BarSelect — globale CSS-Specificity gewann.

Fix:
- Neue `.native-control`-Klasse in index.css setzt mit `revert` alle
  pill-Overrides zurueck → WebKit/macOS rendert wieder Default-Combobox
- BarSelect: className="native-control" am <select>
- Custom-Massstab-Input: className="native-control"

Andere Selects im Projekt behalten den pill-Look (Ebenen-/Geschoss-Manager
etc.) — nur die Oberleiste opt-out.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:14:53 +02:00
karim 536d42f097 BarSelect/BarButton: bare native chrome auch im Ruhezustand
User-Wunsch: nicht nur das Popup, auch das Feld selber soll system-nativ
aussehen. Vorher hatten die <select>-Elemente noch pill-Background +
Border + Radius — das uebermalte WebKits Native-Look.

BarSelect: alle Styling-Overrides entfernt (background/border/border-radius
weg). Nur Hoehe/Breite/Font/colorScheme bleibt — macOS rendert jetzt das
gewoelbte System-Combobox-Feld inkl. nativem Drop-Caret.

BarButton: analog umgestellt — kein eigener Container, native <button>-
Chrome. active-State via accent-color (CSS) + Icon-Tint.

Custom-Mode-Massstab-Input (1:N): pill-Styling weg, nativer <input>.

joinedRight/joinedLeft Props bleiben akzeptiert aber sind no-ops — native
Felder lassen sich nicht visuell verketten.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:12:15 +02:00
karim 2e6dc44923 BarSelect: system-natives <select> + colorScheme:dark (Versuch Vectorworks-Style)
User-Wunsch: Dropdowns wie bei Vectorworks — also nativer OS-Look statt
Custom-Caret + custom Popup.

BarSelect umgebaut:
- appearance:none entfernt → WebKit/macOS rendert seinen eigenen Down-Caret
- custom <Icon arrow_drop_down> entfernt
- pill-Container weg, das <select>-Element selbst traegt jetzt direkt das
  pill-Styling (Hintergrund, Border, Border-Radius)
- colorScheme: 'dark' inline und auch global in index.css gesetzt
  damit macOS-Native-Controls die Dark-Mode-Variante rendern

index.css:
- :root color-scheme: light (default)
- @prefers-color-scheme: dark → color-scheme: dark
- Wirkt auf alle nativen Controls (selects, scrollbars, checkboxen)

Resultat: Klick auf ein Dropdown oeffnet das echte macOS-Popup-Menue
(wie in Vectorworks) statt das HTML-Browser-Dropdown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:08:56 +02:00
karim c8286b931b About-Dialog als eigenes Fenster (Eto-Form + WebView) statt Inline-Modal
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>
2026-05-20 22:07:23 +02:00
karim 76572968ca Oberleiste: Icon roh + Pill-Dropdowns, About-Modal, Overrides/Kombi migriert
User-Feedback: Icon-Box war zu plakativ, lieber roh daneben + Pill-Form.
Versionsnummer raus, Logo-Klick → About-Fenster.

BarSelect umgebaut:
- Icon roh links (vorher: separates Icon-Kompartiment mit Border)
- Select-Container pill-foermig (border-radius 999, vorher 4)
- joinedRight wird zu flachen rechten Pill-Kanten (statt rechteck)

BarButton: pill-foermig + joinedLeft mit flacher linker Kante.

View-Toggle: Pill-Container statt Rechteck.
Massstab Live-Zoom-Chip: Pill-Form.
Custom-Scale-Input: Pill-Form.

Logo:
- Versionsnummer-Span entfernt
- ganzes Logo wird zu Klick-Button → AboutModal-State
- AboutModal: zentrierter Backdrop-Dialog mit Launcher/Plugin-Versionen,
  Autor (Karim Gabriele Varano), Website (gabrielevarano.ch),
  Lizenz (Proprietaer)

Overrides + Kombi migriert (war noch im alten Stack-Layout):
- Overrides: BarButton (Toggle) + BarSelect (Preset) joinedRight + BarButton
  (Settings) joinedLeft, palette-Icon fuer Preset-Picker
- Kombi: BarSelect (layers-Icon) mit allen Aktionen im Dropdown
  (Speichern, Loeschen, Bearbeiten) + BarButton (edit) joinedLeft
- Stack-Layout (label-Spalte + 2 Reihen) ist weg, jetzt inline

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:00:07 +02:00
karim c22aef6b65 Oberleiste Vectorworks-Style: Icon links + rechteckige Bar-Widgets
User-Referenz: Vectorworks Topbar. Drei Cues uebernommen:
- Icon-Kompartiment links im Dropdown (statt UPPERCASE Group-Label oben)
- Rechteckige Form mit dezenter Trennlinie zwischen Icon-Box und Inhalt
- Verbundene Widgets (Select + Settings-Gear) als eine visuelle Einheit

Neue Komponenten in OberleisteApp.jsx:
- BarSelect: [Icon | Native-Select | Caret] in einer 24px-Container-Box.
  appearance:none entfernt den nativen Pfeil, eigener arrow_drop_down rechts.
  joinedRight-Flag fuer nahtloses Anschliessen an einen BarButton.
- BarButton: quadratischer Icon-Button, joinedLeft kein doppelter Border,
  active=true → Accent-Background (fuer Toggles wie Print-View).

Migrierte Gruppen:
- View: 5 Toggle-Buttons in einem zusammenhaengenden Rahmen (kein Group-Label),
  selektierter Button accent-gefuellt analog VW Custom-View-Highlight
- Display: BarSelect (lightbulb icon)
- Masse: BarSelect joinedRight + BarButton joinedLeft (zahnrad)
- Massstab: Live-Zoom-Chip (eigene Box), BarSelect (straighten icon),
  BarButton (percent / fit_screen / center_focus_strong)
- Print/Edit-Toggle: BarButton mit active-State

Stack-Block (Overrides + Kombi) bleibt erst — separate Architektur,
nicht im Vectorworks-Vergleich enthalten. Migration spaeter wenn Bedarf.

PILL_H bleibt fuer nicht-migrierte Stellen, BAR_H = 24 fuer die neuen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 21:52:36 +02:00
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