Commit Graph

76 Commits

Author SHA1 Message Date
karim 736325fba1 Wand-Grips + Schnitt-Grips + Referenz-Sublayer pro Bauteil + Print-Auto-Hide
Custom-Grip-Overlays via DisplayConduit + MouseCallback:
- wand_grips.py: dicke klickbare Marker an wand_axis-Endpunkten, auch
  wenn die Referenz-Layer ausgeblendet ist. GetPoint mit fixem Anker.
- schnitt_grips.py: 3 Marker pro Schnitt (P1, P2, Mid). Mid translatiert
  ganze Linie, P1/P2 verschieben Endpunkt. Hide Clipping-Planes waehrend
  GetPoint damit kein Verbots-Cursor durch Locked-Edges erscheint.
  skip_view=True bei Re-Activate damit Drag nicht in Section springt.

Referenz-Architektur umgebaut:
- wand_axis + oeffnung_point liegen jetzt unter <Geschoss>::20_Waende::
  20r_Referenz statt eigener top-level 19_Referenzlinien-Ebene.
- Migration v4 zieht existierende Sources auf den neuen Pfad.
- Toggle in Oberleiste keyword-driven: findet alle 'Referenz'-Sub-Ebenen
  rekursiv, toggelt alle Praefixe gemeinsam. Bauteil-uebergreifend.

Oberleiste-Layout:
- Druck-Ansicht-Button hoch neben Massstab-Dropdown (Reihe 1).
- Referenzlinien-Toggle in Reihe 2 neben Zoom-Pill, symmetrisch zum
  Druck-Button. Zoom-Pill auf 3 Buttons reduziert.
- Print-View AN → Referenz-Layer automatisch ausblenden, Snapshot
  restored beim Ausschalten.

Fix: clear_schnitt_clipping respektiert Mode=Locked nicht — vor Delete
auf Normal-Mode wechseln + Modify damit's persistiert. Schnitt-Loeschen
raeumt Clipping-Planes jetzt sauber auf.

Fix: Schnitt-Doppelklick-Handler aktiviert nur bei expliziter Schnitt-
Auswahl, ignoriert andere Selektionen.

Fix: _send_state Selection-Detection mit Source-ODER-Volume-Fallback —
Fenster-Properties erscheinen jetzt auch wenn oeffnung_point auf hidden
Referenz-Layer liegt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 20:58:06 +02:00
karim 059cbf8d4d Schnitt/Ansicht-Feature + Terrain-Volumen + Geschoss-Add-Dialog
Schnitt-Feature V1+V2:
- Neues rhino/schnitte.py mit Pick-Workflow, Activation (Clipping-Planes +
  Parallel-View), 2D-Plan-Symbol auf 18_Schnittlinien-Sublayer
- Doppelklick auf Symbol aktiviert den Schnitt
- Schnitt-Settings (cutAtLine/Tiefe/Höhen/Blickrichtung) im GeschossSettingsDialog
- View-Snapshot + Restore beim Wechsel Schnitt → Geschoss
- Symbol-Cleanup bei Delete via normalem Ebenen-Menü

Terrain als Volumen:
- swisstopo.volumize_terrain_object: Skirt + Bottom-Cap auf Mesh/Brep
  damit Clipping-Planes gefuellte Querschnitte erzeugen
- UI im SwisstopoApp mit Nachbearbeitung-Section + Tiefen-Eingabe

Geschoss-Add mit Dialog:
- + im GeschossManager oeffnet 3-Optionen-Picker (Geschoss/Schnitt/Zeichnung)
- Geschoss-Dialog mit Anker-Dropdown, Position über/unter, Auto-Name,
  Höhen-Prefill aus Anker

Fix: _send_state fallback — Element gilt als selektiert wenn Source ODER
Volume in der Selection ist (robust gegen Layer-Visibility wenn Referenz-
linien-Layer im aktuellen Mode versteckt ist)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 18:28:59 +02:00
karim 3277f61ced Oeffnungen-Sublayer + Sturzlinien + Referenz-Layer + Pill-Inputs + Anordnen-Pill
- Oeffnungen-Subtree (Rahmen/Glas/Tuerblatt/Sims/Pane/Schwung/Sturz) als
  nested Children unter WAENDE im dossier_ebenen-Tree registriert + per-Kind
  Material (Glas mit Transparenz)
- Sturzlinien bei 1:100 Tueren mit Innen/Aussen/Beide/Keine-Dropdown
- Referenzlinien-Layer (19) als eigene Ebene fuer wand_axis + oeffnung_point
- Swisstopo Patch-Terrain (Brep.CreatePatch) ersetzt das falsche Loft
- Pill-Style fuer alle Inputs zentral via index.css
- 2x2 Anordnen-Pill in der Oberleiste (BringToFront/Forward/Backward/SendToBack
  via Rhinos DisplayOrder, kein Z-Offset)
- Chevron-Verschiebung in Ebenen-Panel ohne dass Siblings shiften
- Fix: _update_ebene_field walked nur Top-Level, nested Sublayer-Style-
  Changes wurden nicht persistiert
- Fix: Sturz-Linetype wurde bei jedem Wand-Regen zurueckgesetzt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 16:07:44 +02:00
karim 3dc6e31374 Tueren-Schwung: sweep_sign-Swap + perp am Tuerblatt bei standard/detail
- sweep_sign war vertauscht — closed_pt landete am falschen Ende des
  Blattes. hinge=links → +1 (extends in +tan), rechts → -1.
- Schwung-Perp-Position:
  - einfach (1:100) → Wand-Innenkante (unchanged)
  - standard/detail (1:50/1:20) → Mitte des Rahmens (= wo das Tuer-
    blatt-Brep sitzt). Damit liegt der Bogen direkt am sichtbaren
    Blatt statt am Wand-Innenrand.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 13:33:53 +02:00
karim 655d368c92 Tueren-Schwung: Live-Update + Hinge-Position auf Wand-Innenkante
- Schwung-Curves-Block VOR dem Volume-Replace-Pfad — sonst greifen
  openAngle/hingeSide/swingInvert-Aenderungen nicht (Volume-Anzahl
  bleibt gleich → continue → Swing-Skip).
- Hinge-Punkt sitzt nicht mehr auf der Wand-Achse sondern auf der
  Wand-Innenkante (half_d * inside-Richtung). Tuerblatt + Bogen
  beginnen damit an der Wandflucht statt in der Wandmitte.
- Rotation-Vorzeichen korrigiert (rad = open_angle * sweep_sign *
  aus_sign) damit Tuere wirklich ins Innere schwingt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 13:23:44 +02:00
karim 0c5f8055a5 Fenster/Tueren LoD + Stile + Phase-3-Ausschnitt-Darstellung + UI-Konsistenz
Fenster/Tueren:
- 3-stufige SIA-400-Darstellung pro Element: einfach (1:100, flache
  Scheibe ohne Tiefe in Wand-Mittelebene), standard (1:50, Rahmen +
  Glas + Sims), detail (1:20, Doppelverglasung).
- Aussenseite-Flag mit Auto-Detection aus der Click-Richtung beim
  Setzen — Sim sitzt automatisch aussen. Im Panel als Umkehren-Toggle.
- Tueren-Rahmen-Typ Zarge|Block — Blockrahmen ragt seitlich raus.
- Rahmen-Offset (m von Wand-Innenseite) ersetzt das 3-Preset Lage-
  Feld. Wirkt auch in der einfachen Darstellung (Pane sitzt auf der
  Rahmen-Mittelebene, nicht in Wand-Mitte).
- Sims nur AUSSEN. Innen entfaellt — der Sim ist gleichzeitig der
  visuelle Indikator fuer die Aussenseite.
- Oeffnungs-Stile: list/save/delete-API mit 6 Default-Presets
  (Fenster Standard/Gross/Bandlage, Tuer Innen/Eingang/Verglast).
  Style-ID per UserString am Objekt persistiert. Im Panel BarCombo
  mit "Aktuelle als Stil speichern…". Beim Rhino-Command "Stil"-
  Option zum Picken vor dem Klick.

Ausschnitt-Darstellung (Phase 3):
- Doc-Level Override dossier_aktive_darstellung gewinnt vor per-
  Object-Setting. Wechsel triggert Regen aller Oeffnungen via neuer
  regenerate_all_oeffnungen-API.
- Ausschnitt-Capture speichert die Darstellung mit, Restore wendet
  sie an und regeneriert.
- Oberleiste-Quick-Switch BarCombo mit 4 Optionen.
- AusschnittSettings-Dialog: Darstellungs-Dropdown.

Gestaltung (SectionStyle Phase 2):
- _set_section_style schreibt per-Object SectionHatchIndex/Scale/
  Rotation/Color mit Multi-Fallback (Property-Namen varieren je
  Rhino-Build). _selection_summary liest die selben zurueck.
- HatchEditor als shared Component fuer Fill + Section.
- geometryKind ignoriert DOSSIER-Source-Curves damit Wand-Selektion
  (Axis + Volume) als 3D klassifiziert wird.

UI-Konsistenz Panels:
- Ebenenkombi zurueck als eigene Section oben im Ebenen-Panel,
  Modelldarstellung-Dropdown an die freigewordene Position in der
  Oberleiste (Row 1 Col 2 im 2x2-Preset-Block).
- BarCombo erweitert: stretch-Prop (Pill waechst auf Container-
  Breite), onSecond/secondIcon/secondTitle fuer 2. Trailing-Button,
  gearIcon-Prop. Plus-Slot immer ganz aussen rechts, Settings-Slot
  direkt nach dem Caret.
- Ebenen + Zeichnungsebenen visuell kohaerent: identisches Padding
  (1px 12px 1px 0), Chevron/Spacer-Slot 12px, Master-Row mit Eye
  16x16 + Lock 14x14, gleiche Border + Borderfarbe. Eye-Icons in
  beiden Panels untereinander ausgerichtet.
- Properties-Container ohne Border (war zuvor accent-gruen, dann
  border — User wollte gar nichts mehr).
- ElementList raus aus dem Elemente-Panel (Uebersicht via Tree-
  Window erreichbar). NeuesElement bleibt voll sichtbar bei
  Selektion (kein Collapse), Properties oben.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 12:34:15 +02:00
karim 9ae8574ab0 Panel-Konsistenz: Gestaltung context-aware + Tabellen-Cleanup
Gestaltung:
- Backend detektiert geometryKind (curve / curveOpen / 3d / mixed),
  ignoriert DOSSIER-Source-Curves (wand_axis etc.) damit die
  Klassifikation einer Wand-Selektion (Axis + Volume) als reines 3D
  durchgeht.
- UI zeigt sektion-spezifische Section-Heads: 2D-Curve → Fill+Pen,
  3D-Solid → Section Style+Boundary. Section Style + Fill nutzen
  jetzt einen gemeinsamen HatchEditor (gleiche Controls).
- _set_section_style schreibt per-Object SectionHatchIndex/Scale/
  Rotation/Color via _try_set_attr-Multifallback.
- Selection-Summary liest dieselben Properties zurueck.
- ColorBar als Pill (borderRadius 999) statt eckig.
- "Attribute"-Header oben raus (war alt + redundant).

Ebenen / Zeichnungsebenen:
- Aktive Zeile wieder als Pill (borderRadius 999) — aber ohne
  Margin/Shift, Inhalt springt nicht. Kein bold-text-change mehr.
- Linker borderLeft komplett raus — vorher leere graue Spalte am
  Panel-Rand sichtbar.
- Inhalt mehr nach links (Padding 8/6 statt 12).
- Zeilen-minHeight 24, kompaktere Icons.
- EbenenSettings: Ebene-Picker auf BarCombo.
- GeschossManager: Gebaeudehoehe-Zeile raus, Stift→Settings-Icon.

ElementeApp:
- Alle btn-contained/btn-outlined → BarToggle (Wand-Aufbau, Raum-
  Align, Treppe-Lage/Modus, Oeffnung-Ref/Rahmen/Fluegel/Glas,
  AutoOverride).
- ReferenzSelector → BarToggle.
- "Neues Element"-Container ohne Box-Border, Header-Slot
  beherbergt Projektuebersicht-Pill statt label.
- Property-Labels (UK/OK/Dicke/...) von text-muted auf
  text-secondary (war zu blass).
- Gruener Accent-Border um Properties-Containern → normaler border.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 01:17:58 +02:00
karim 15fb0a6037 Elemente: BIM Project Browser + Properties-Satellite-Window
Zwei neue Satellite-Windows (analog Kamera/Text-Editor):

1) Projekt-Übersicht (elemente_uebersicht.py + ElementeUebersichtApp.jsx)
   - Tree Geschoss → Kind → Element-Instanzen
   - Suche + Kind-Filter-Chips
   - Klick = selektieren in Rhino, Shift+Klick = zoomen
   - Erreichbar via account_tree-Button im Elemente-Panel-Header

2) Properties-Satellite (elemente_properties.py + ElementePropertiesApp.jsx)
   - Eigenes Fenster mit der PropertiesView (gemeinsame Komponente)
   - Live-Updates: elemente._send_state forwarded zu satellite-bridge via sticky
   - Erreichbar via open_in_new-Icon oben rechts in der Properties-Karte
   - Inline-Properties im Panel bleiben — Satellite ist für mehr Platz

Plus ElementeApp-Cleanup:
- ElementList (alle Elemente-Liste) raus — wird jetzt von Projekt-
  Übersicht abgedeckt.
- Properties springen bei Selektion nach oben, NeuesElement bleibt
  voll sichtbar darunter (kein Scrollen mehr).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 01:17:31 +02:00
karim 26c7d9e67d Text-Editor: Rhino RTF-Dialekt + Round-Trip + Oberleiste-Sync
Lange Iteration mit dem Rhino TextEntity-RTF-Parser (siehe MEMORY:
rhino_textentity_rtf_limits.md). Finale Form:

- RTF-Body: per-Segment {\fN\cfN\b\i\ulnone seg}-Groups, \par
  zwischen Groups als Linebreak, { }-Space-Group fuer Leerzeilen
  (Rhino collapsed sonst aufeinanderfolgende \par). \fs (Font-Size)
  ist NICHT unterstuetzt → eine Size pro TextEntity (global).
- htmlToRuns: emittiert \n VOR Block-Elementen wenn schon Content
  davor — fixt nested <div>A<div>B</div></div> die sonst A+B ohne
  Trenner als ein Run liefern.
- Round-Trip-Erhaltung: editor.innerHTML 1:1 als UserString
  "dossier_text_html" persistiert, beim Reopen direkt gesetzt
  (kein runsToHtml-Konvertieren das Zeilen verlieren kann).
- Oberleiste-Editing: in-place modify von obj.Geometry + Commit-
  Changes statt Duplicate+Replace (Mac Rhino gibt False zurueck
  bei RichText-Klonen). Plus _patch_rtf_b_i_ul: regex-flippt
  \b/\b0, \i/\i0, \ul/\ulnone global in der RTF damit Bold/Italic/
  Underline OFF in der Oberleiste auch wirklich auf DOSSIER-Texte
  greift (per-Segment-Codes wuerden te.Font-Aenderung sonst
  uebersteuern).
- Stil-ID am Text persistiert + von read_selection_settings
  zurueckgelesen → Stil-Dropdown spiegelt Selektion.
- Editor neu: V-Align (Top/Middle/Bottom), Mask-Type (None/Viewport/
  Solid) mit Farb-Picker, Case-Transform (upper/lower/capitalize/
  invert), Masstaeblich-Toggle (AnnotationScalingEnabled),
  Symbol-Popover, Frame-Optionen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 23:56:16 +02:00
karim ae80185064 Text-Editor: Toggle "Masstäblich" (AnnotationScaling)
Neuer Pill-Button in Row 3 neben "Zur Kamera": setzt
TextEntity.AnnotationScalingEnabled. Property-Name wird in mehreren
Varianten probiert (Rhino 8 API hat das je nach Build leicht anders
benannt). Zustand wird zusaetzlich als UserString
"dossier_text_scaled" persistiert, sodass open_for_edit den Toggle
auch dann korrekt restored wenn die API-Property nicht gelesen
werden kann.

Default = an (entspricht aktuellem Verhalten: Text skaliert mit der
Annotation-Scale des Layouts).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:23:30 +02:00
karim 6b3421e7af Text-Editor: V-Align + Mask-Type/Color + Case-Transform
Erweitert den Editor um die fehlenden Rhino-Text-Optionen:

- Vertical Alignment (Top/Middle/Bottom) als 3 Pill-Buttons in Row 2.
  Backend: text_create._apply_valign mit TextVerticalAlignment-Enum.
- Mask-Type Dropdown (None/Viewport/Solid). Bei Solid erscheint ein
  Mask-Color-Picker. MaskMargin disabled wenn Type=none.
  Backend: schaltet te.MaskEnabled + te.MaskUsesViewportColor +
  te.MaskColor entsprechend.
- Case-Dropdown in Row 2: upper/lower/capitalize/invert. Wirkt nur
  auf die Selektion via execCommand insertText.

open_for_edit liest valign + maskType/Color/Margin aus bestehendem
TextEntity zurueck, damit beim Re-Open der Zustand erhalten bleibt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:08:51 +02:00
karim de6f84346c RTF: Leerzeilen via {space}\par-Filler zwischen \par
Rhinos Parser collapsed mehrere \\par in Folge zu einem einzelnen
Linebreak. Loesung: jeder zusaetzliche \\par bekommt einen
{ }-Space-Group vor sich, damit der leere Paragraph "Inhalt" hat
und als echte Leerzeile gerendert wird.

Why: User-Test "TITEL\\n\\nTEXT" renderte als zwei aufeinander-
folgende Zeilen ohne Leerzeile dazwischen, obwohl die zweite \\n
korrekt in pending_pars gezaehlt wurde.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:05:11 +02:00
karim 29699b5eda RTF: Groups + \par zwischen Segmenten
Zurueck zur Groups-Form ({...} pro Run), aber mit Two-Pass-fonttbl
das Pass 1 bereits korrekt populiert. Zwischen den Groups \\par
fuer Paragraph-Breaks — der einzige Linebreak-Token den Rhinos
TextEntity-Parser tatsaechlich rendert. \\line aus dem letzten
Versuch wurde von Rhino ignoriert.

pending_pars-Counter sammelt Newlines ueber Run-Grenzen hinweg,
sodass auch mehrere aufeinanderfolgende \\n (= Leerzeilen) erhalten
bleiben. Fuehrender \\par wird unterdrueckt (first_emitted-Flag),
damit der Text nicht mit einer Leerzeile beginnt.

Why: Per-Run-Groups isolieren Font-State (Fix fuer "letzte Schrift
dominiert"), waehrend \\par die Mehrzeiligkeit liefert die Rhino
versteht. Two-Pass garantiert dass fonttbl alle benutzten Fonts
enthaelt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:01:02 +02:00
karim 5abf1c0137 RTF: \line statt \par fuer Newlines zwischen Runs
Rhinos TextEntity-RTF-Parser rendert \par offenbar nicht als
Zeilenumbruch innerhalb eines Textfeldes. \line dagegen funktioniert
als soft line break.

Globales pending_newlines-Counting ueber alle Runs hinweg: jedes \n
im Text-Run wird zu einem \line, der erst VOR dem naechsten echten
Text emittiert wird. Damit bleiben auch Leerzeilen (mehrere \n
hintereinander) als mehrere \line erhalten.

Why: User-Vergleich Screenshots — WYSIWYG zeigt korrekte Leerzeile
zwischen Heading und Paragraph, Rhino rendert beide Runs auf der
gleichen Zeile konkateniert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 08:38:50 +02:00
karim 91bc03184e RTF: Two-Pass fonttbl + Leerzeilen-Fix + wrap-Diagnose
Log-Analyse vom User: RTF preview zeigte
  '{\\fonttbl{\\f0\\fnil\\fcharset0 Georgia;}}'
ABER der Body nutzt
  '\\f1\\cf1\\fs20\\b ... Lorem ...'
\\f1 ist NICHT in der fonttbl! → Rhino fallback auf Default
\\cf1 ist NICHT in der colortbl (es gibt keine)! → ignoriert

Root cause: ich hatte die fonttbl/colortbl eagerly geschrieben BEVOR
die Runs ueberhaupt verarbeitet waren. fonts/colors-Listen wurden waehrend
des Body-Loops um neue Eintraege erweitert, aber der schon-emittierte
Header sah die nie.

Fix: Two-Pass.
PASS 1: Body bauen, dabei font_idx/color_idx aufrufen → fonts/colors-
Listen werden komplett gefuellt.
PASS 2: RTF-Header schreiben mit JETZT vollstaendigen Tables, dann Body
anhaengen.

Plus Leerzeilen: aufeinanderfolgende \\n in den Runs erzeugen jetzt
ein leeres Paragraph mit Space (" "), damit Rhinos Parser den \\par
nicht mit dem naechsten kollabiert. Resultat:
  Lorem...\\par
   \\par         ← Leerzeile (echtes Space im leeren Paragraph)
  Consectetur...

Plus Frame-Wrap-Diagnostic: "[TEXT-EDITOR] wrap: width=... applied_attr=
FormatWidth/TextWidth/None" damit ich sehen kann ob die Wrap-Property
ueberhaupt gesetzt wird in dieser Rhino-Version.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 08:15:26 +02:00
karim 2d48a6ed3a RTF: inline state codes + SetRichText method + Diagnose-Print
User: Mixed-Fonts gehen immer noch nicht — Rhino zeigt nur die letzte
gesetzte Schrift, Bold-State auch verloren.

_runs_to_rtf neu: klassisches Inline-RTF ohne {} Groups. Pro Run werden
ALLE Codes explizit gesetzt (auch Reset-Codes \\b0 \\i0 \\ulnone \\cf0
\\nosupersub) und alle in EINER Zeile mit dem Text:

  \\pard \\f0\\cf0\\fs60\\b\\i0\\ulnone\\nosupersub Georgia bold
  \\par \\f1\\cf0\\fs20\\b0\\i0\\ulnone\\nosupersub Helvetica regular

Damit ist klar: jeder Run hat eigene state-Definitions. RTF-Parser
nimmt nicht "letzte Code wirkt auf alles".

_commit-Reihenfolge bei RichText geaendert: KEIN _apply_font wenn RTF
verwendet wird. te.Font wuerde sonst die per-Run \\fN Codes
ueberschreiben.

Method-Switch: te.SetRichText(rtf, dimstyle) zuerst probiert (robuster
API), te.RichText property als Fallback, _apply_font als letzter
Notfall.

Diagnostic: RTF wird jetzt mit Length + 300-char-preview gedruckt
("[TEXT-EDITOR] RTF len=... preview=..."). Bitte Log copy-pasten bei
weiteren Issues damit ich seh was tatsaechlich Rhino erreicht.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 02:19:04 +02:00
karim 211078d229 Text-Editor: Style-Picker focus-restore + Runs-Round-Trip beim Reopen
User-Bugs:
1. Stil-Picker setzt Toolbar-State aber Editor zeigt alle Texte gleich
2. Beim Reopen (Doppelklick auf DOSSIER-Text) ist Editor leer/unformatiert

Fix 1 — applyStyle focus-restore:
Style-Picker stiehlt Focus → savedRange ist da, aber execCommand
ohne Focus auf editable schreibt nichts. Fix: editorRef.focus() +
restoreSelection() VOR execCommand. Plus Size jetzt via inline-span
(execCommand fontSize geht nur 1-7 Scale, kein Pixel).

Fix 2 — Runs persistieren als UserString fuer Round-Trip:
- _commit: runs als JSON in attrs.SetUserString("dossier_text_runs")
  abgelegt zusammen mit dem dossier_text-Tag
- open_for_edit: liest UserString, parsed JSON → initial_runs
- Bridge INIT: sendet initialRuns mit
- Frontend onMessage INIT: wenn initialRuns vorhanden → runsToHtml()
  baut HTML mit spans (font-family, font-size, color) + Tags (b/i/u/
  sup/sub) — Editor zeigt jetzt beim Reopen das tatsaechliche reiche
  Format statt PlainText im Default-Helvetica

runsToHtml: pro run text/style-Splittung per \n → <br>. Escaping fuer
&<> damit kein XSS / Parse-Fehler.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 02:13:42 +02:00
karim 3bd949e590 RTF: pro-Run-Groups + Newlines als \par zwischen Groups
User-Bug: Heading (Helvetica Bold) + Paragraph (Georgia Regular) als
2 Zeilen ergibt in Rhino: alle Text in einer Zeile, mit Bold vom
Heading aber Font+Size vom LETZTEN definierten Paragraph (Georgia).

Root cause: Mein RTF-Generator emitted alle Codes hintereinander ohne
{}-Grouping. RTF-Parser ohne Scope nimmt das letzte \\f und \\fs als
globalen State und appliziert auf ALLES.

Fix _runs_to_rtf:
- Pro Run: text per \\n splitten, zwischen Segmenten \\par emitten
- Jedes nicht-leere Segment wird in {} Group gewrapped mit eigenen
  Codes (\\fN \\cfN \\fsN \\b \\i \\ul \\super \\sub)
- Nur AKTIVE Toggles in der Group (kein \\b0/\\i0/\\ulnone mehr —
  Default ausserhalb der Group ist plain)
- Resultat: {\\f0\\b Lorem ...}\\par {\\f1 Consectetur ...}
  Jede Group ist isoliert, kein Cross-Run-State-Leak

_escape_no_par: \\n bleibt als Literal-Newline durchgelassen (splitting
geschieht im outer loop, nicht im escape).

_commit-Reihenfolge nochmal aufgeraeumt:
1. Plane
2. Defaults (Height, Font, Align) — gilt fuer alles
3. Content (RichText wenn nontrivial, sonst PlainText)
4. Wrap (FormatWidth + TextIsWrapped) — NACH RichText damit nicht
   zurueckgesetzt
5. Frame/Mask/DrawForward

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 02:07:46 +02:00
karim 1596bbd941 Text-Editor: B/I/U sync via queryCommandState + RTF-Reihenfolge fixen
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>
2026-05-21 02:00:03 +02:00
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 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 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 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 2ee4688fe3 Masse-Dropdown in Oberleiste + Satellite-Settings statt Dimensionen-Inline
User-Feedback: Mass-Style passt nicht ins Dimensionen-Panel, und der
Name "Mass-Style" gefaellt nicht. Umzug in die Oberleiste (analog Display)
+ Zahnrad oeffnet eigenes Settings-Fenster. UI-Begriff jetzt "Masse".

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 18:22:48 +02:00