PHASE 1 — Bulk-Apply auf mehrere selektierte Raeume:
- Backend _cmd_apply_raum_stil: leere ids im Patch → nimmt automatisch
alle aktuell SELEKTIERTEN raum_outlines aus dem Doc (Bulk-Fallback)
- Frontend sendet leere ids fuer die Default-Bulk-Variante. User
selektiert N Raeume im Viewport → klickt Stil im Properties-Picker →
Stil wird auf alle angewendet. Inkludiert immer den Properties-Raum.
PHASE 2 — Stil-Manager als Settings-Tab:
- Neuer Tab "Raumstile" in ProjectSettings-Dialog
- Liste aller Stile mit Drag-Reorder (HTML5 native), Inline-Rename,
Duplicate (content_copy-Icon), Delete (mit Confirm)
- Mini-Preview: zeigt Layout-Struktur (z.B. "nummer·name / funktion / area")
und der Name wird mit Font/Bold/Italic des Stils gerendert als Vorschau
- Backend _cmd_reorder_raum_stile + _cmd_duplicate_raum_stil in elemente.py
- ProjectSettings-Bridge dispatcht direkt zu elemente.load/save_raum_stempel_stile
(kein Roundtrip via Elemente-Bridge noetig)
- STILE_UPDATED-Message nach jeder Op pusht die neue Liste zum Dialog
- params bei _open_project_settings enthalten jetzt raumStempelStile + fonts
Bridge-Exports: reorderRaumStile / duplicateRaumStil in rhinoBridge.js
UX-Konsolidierung: Texthoehe und Ausrichtung wurden bisher doppelt
gesetzt (Panel + Oberleiste-Text-Block). Jetzt nur noch via Oberleiste
wenn der Stempel selektiert ist — Panel zeigt nur noch den Skala-Modus
(fix/masstab) als kompakten Dropdown.
Backend: _sync_raum_stamps_to_source spiegelt jetzt auch
TextHorizontalAlignment vom Stempel zur Source (Left/Center/Right →
links/mid/rechts). Damit kommt die User-Wahl aus der Oberleiste sauber
in die UserStrings — und wird beim naechsten Regen + bei Stil-Speichern
korrekt uebernommen.
Frontend RaumProperties:
- Ausrichtung-Zeile (3 BarToggles) raus
- Hoehe-Zeile reduziert auf Skala-Modus-Dropdown:
"fix · Hoehe = Oberleiste-Wert (m)" | "masstab · skaliert mit Plan-Massstab"
- Local-State txtH + setTxtH entfernt (unused)
- Stempel-Stil-Speichern liest weiter raum.txtH + raum.align (kommen
jetzt vom Sync) → captured korrekt
UserString dossier_raum_stil_id auf der raum_outline. Wird gesetzt:
- bei APPLY_RAUM_STIL → schreibt sid auf alle Ziel-Raeume
- bei SAVE_RAUM_STIL mit applyToIds=[…] → schreibt neue sid auf
uebergebene Raeume (Frontend nutzt das beim "+ Speichern…"-Flow um
den aktuellen Raum mit dem neuen Stil zu verknuepfen)
_read_meta liest stil_id mit → State-Emit feldet "stilId" zum Raum.
Frontend's Stil-Dropdown markiert dadurch automatisch den aktiven Stil
(via activeStilId = raum.stilId).
Manuelle Edits (UPDATE_ELEMENT) touchen stil_id NICHT — User kann
seinen Stil leicht tweaken ohne dass die Verknuepfung verloren geht.
Wenn der Tweak weit weg ist, kann er den Stil aktualisieren via
"+ Aktuelle Settings als Stil speichern" (uebernimmt dann mit der
NEUEN stil_id).
Aggregiert alle raum_outline-Flaechen nach raum_sia-Klassifikation
(hnf/nnf/vf/ff) und zeigt sie als kompakte Mini-Tabelle direkt unter
dem Geschoss-Header in der Project-Browser-Uebersicht.
Backend (_build_overview):
- Neuer Returnschluessel siaBilanz: {geschossId: {hnf, nnf, vf, ff,
ohne, nf, total, count}} in m^2
- NF = HNF + NNF (Nutzflaeche nach SIA 416)
- Raeume ohne SIA-Tag landen in "ohne"
Frontend (ElementeUebersichtApp):
- Direkt unter dem Geschoss-Header eine Inline-Tabelle mit nur den
Klassen die > 0 sind (kein Spam wenn nichts klassifiziert ist)
- NF separat hervorgehoben (Accent-Farbe) als wichtigste Kennzahl
- Read-only, aktualisiert sich mit jedem state-emit (Raum-Aenderung,
SIA-Tag setzen, neue Raeume) automatisch
Damit User wiederkehrende Stempel-Configs (Wettbewerb / Bauantrag /
Mobiliar etc.) nicht jedes Mal neu klicken muss.
Backend (elemente.py):
- Storage in doc.Strings dossier_raum_stempel_stile als JSON-Array
- load_raum_stempel_stile / save_raum_stempel_stile Helpers
- Bridge-Handler:
- SAVE_RAUM_STIL: upsert by id (neu wenn leer)
- DELETE_RAUM_STIL: remove by id
- APPLY_RAUM_STIL: schreibt Stil-Felder auf Raum-IDs + Regen
- _RAUM_STIL_FIELDS umfasst font/bold/italic/txtH/txtModus/align/
rundung/fuellung/showSia/layout (alles was die Optik bestimmt)
- raumStempelStile im STATE-Emit zum Frontend
Frontend:
- saveRaumStil/deleteRaumStil/applyRaumStil in rhinoBridge.js
- RaumProperties: neue "Stil"-Sektion oben mit Dropdown
* gespeicherte Stile + "+ Aktuelle Settings als Stil speichern" +
"🗑 Aktiven Stil loeschen"
* Klick auf Stil → applyRaumStil mit aktueller Raum-ID
- Beide PropertiesView-Aufrufe (inline + satellite) bekommen die Liste
Statt jeden Wert im Code zu konvertieren wird sichergestellt dass das
Doc in der gewuenschten Unit ist:
- defaults.unit ('meters'|'millimeters'|'centimeters') in
dossier_project_settings, Default 'meters'
- ProjectSettings-Dialog "Voreinstellungen" Tab: neue Sektion
"Arbeitseinheit" mit Toggle-Group fuer m/cm/mm
- get_project_unit() + get_project_unit_enum() Helper in rhinopanel
- startup._check_doc_unit() prueft beim Doc-Open ob ModelUnitSystem
matched — bei Mismatch Eto-MessageBox "Doc auf X umstellen?"
- "Yes" ruft _-Units _Model _<Unit> _Yes (Geometrie wird mit-skaliert)
- "No" setzt doc.Strings-Flag dossier_unit_checked → keine erneute Frage
- Check laeuft beim _on_doc_opened-Hook + initial fuer aktives Doc
Vorgehen ist deutlich sauberer als der vor-revert unit-aware Code
(135 Zeilen Konvertierungslogik vs 80 Zeilen Check+Convert).
Neuer UserString dossier_raum_txt_modus = "fix" | "masstab" (default fix).
- fix: raum_txt_h ist Meter (Modellhoehe, bisheriges Verhalten)
- masstab: raum_txt_h ist Paper-mm. Render-Hoehe (m) =
paper_mm * applied_scale / 1000 — wird zur Render-Zeit aus
massstab.get_applied_scale_ratio() gelesen, Fallback 1:100.
Massstab-Sync:
- massstab._apply_scale ruft nach Skala-Wechsel elemente.regen_masstab_raeume(doc)
→ alle Raeume im masstab-Modus regennen automatisch, Texthoehe folgt der
neuen Skala (z.B. Switch 1:100 → 1:50 halbiert die Modellhoehe).
_sync_raum_stamps_to_source masstab-aware: im masstab-Modus wird die
TextHeight am Stempel NICHT zurueck auf raum_txt_h gespiegelt (sie ist
abgeleitet, nicht die Wahrheit) — sonst waere der naechste Regen sofort
falsch positioniert. Offset + Font/Style werden weiterhin gespiegelt.
UI: Modus-Toggle "fix m" / "masstab mm" + Hoehen-Input + Einheits-Suffix
in RaumProperties zwischen Ausrichtung und Funktion.
- Neues Template rhino/templates/dossier_3d.ini fuer perspektivische Views
- Registry-Loop in oberleiste.py generalisiert (Plan + 3D + Material +
Raytracing) — Material/Raytracing skippen wenn kein Template vorhanden,
um Cycles-Pipeline-Clone-Crash zu vermeiden
- Guid-Replace praezisiert: nur Section-Header-Guid, nie PipelineId
- Plan-spezifische ini-Patches auf target_name=="Dossier Plan" gegated
- Auto-Assign in startup.py: Parallel-Viewports -> Plan, Perspective -> 3D,
einmal-pro-Doc via doc.Strings-Flag (User-Overrides bleiben)
- schnitte.activate_schnitt setzt Dossier Plan explizit (Hatches auch in
Schnittperspektive sichtbar)
- GestaltungApp Section-Block neu strukturiert: PenBlock fuer 3D als 'Fill'
innerhalb der Section, Solid-Fill-Toggle entfernt (war Duplikat)
Beim Hinzufuegen oder Importieren eines Library-Items wird automatisch
ein PNG-Thumbnail vom Item generiert (Top-View, 128x128) und in
library/previews/<id>.png abgelegt. Frontend rendert die Previews als
Base64-Data-URIs (sicher gegen WebKit-file://-Restriktionen).
library.py:
- _previews_dir(): legt previews/-Folder an
- _preview_rel_for(asset_rel): predictable PNG-Pfad pro Item
- _capture_thumbnail_of_objects(): hided andere Objekte temporaer,
switcht auf Top-Parallel, ZoomBoundingBox, CaptureToBitmap → PNG,
restored Viewport + Hidden-State
- read_preview_data_uri(): liest PNG + encoded als data:image/png;base64
- Hook in convert_to_3dm_via_import (vor Cleanup) + save_selection_to_asset
rhinopanel.py:
- _enrich_library_items_with_previews(): haengt previewDataUri an
jedes Item das ein preview-Feld hat
- Initial-Params + _send_library + ElementeBridge._cmd_list_library
liefern angereicherte Items
- _add_library_file + _save_selection_as_library setzen preview-Pfad
im Item wenn Thumbnail-Datei existiert
Frontend:
- SymbolPicker.ItemPreview: rendert <div backgroundImage> mit Base64-URI
wenn vorhanden, sonst Icon-Fallback
- ProjectSettingsDialog Symbole-Tab: List-Row + Detail-Identity zeigen
Thumbnail (32px in Liste, 56px im Detail), Icon nur als Fallback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User selektiert in Rhino → klickt im Symbole-Tab 'Aus Auswahl' →
Selection wird in eine .3dm-Datei verpackt + Library-Item entsteht
(oder bestehendes wird aktualisiert).
Backend (library.py):
- save_selection_to_asset(doc, target_name): erstellt File3dm aus der
aktuellen Selection, packt Geometry relativ zu BoundingBox.Min (Block-
Origin am Ursprung), schreibt nach library/assets/
Backend (ProjectSettingsBridge):
- SAVE_SELECTION_AS_LIBRARY-Handler: holt Selection, fragt bei neuen
Items via Rhino-GetString nach Name, schreibt .3dm, fuegt Item zum
Manifest oder updated bestehendes (variant '2d'/'3d')
Frontend (Symbole-Tab):
- List-Footer: 'Aus Datei' + 'Aus Auswahl' Pills (neues Item)
- Pro 2D/3D-Slot im Detail: 'Datei wählen' + 'Aus Auswahl' Pills
(Variante eines bestehenden Items befüllen)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Schema-Erweiterung:
- _PROJECT_SETTINGS_DEFAULTS hat jetzt 'project'-Block mit
name / number / address / bauherr / architekt / notes / projectZeroMum
- _normalize_project_meta stripped Strings + clampt mum als float
- load/save_project_settings handeln das 'project'-feld
- save_project_settings spiegelt projectZeroMum auch in den Legacy-Key
dossier_project_zero_mum (fuer Geschoss-Settings-Dialog)
- load_project_settings liest Legacy-Key als Fallback wenn neuer Wert
noch nicht gesetzt
UI:
- InlineTextField + TextareaField Helpers (Pill-Stil)
- Projektdaten-Section in Voreinstellungen-Tab:
Name, Projekt-Nr., Adresse, Bauherrschaft, Architekt:in,
EG-Nullpunkt m.ü.M (mit Hinweis auf Swisstopo-Nutzung), Notizen
Swisstopo:
- _cmd_open_swisstopo_dialog laedt Projekt-Adresse + sendet projectAddress
im SWISSTOPO_STATE
- SwisstopoApp: vorbelegt searchText mit projectAddress wenn Feld leer
ist (User-Input wird nicht ueberschrieben)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Material ist jetzt rein 3D — Section-Hatch (2D-Schnitt) wird nicht mehr
am Material definiert, sondern am Layer (via Rhino-Layer-Dialog oder
spaeter via Ebenen-Settings + neuer Hatch-Tab im Project-Settings).
Schema-Aenderungen:
- _normalize_material: hatch + scale entfernt
- _MATERIAL_LIBRARY (elemente.py): hatch + scale aus allen Builtin-Mats
- _get_all_materials: ohne hatch
- _send_state materials payload: nur {name, color}
- Library import_material: PBR + Texturen werden weitergegeben
Backend:
- _ensure_material_sublayer: erstellt Sublayer mit Color, RESETTET aber
alten SectionHatchIndex auf -1 (= "kein eigener Hatch") damit
Inheritance/User-Override greift. Vorher wurden alte Material-Hatch-
Werte da haengen geblieben.
Frontend:
- MaterialDetail: Schraffur-Section entfernt
- hatchPatterns-Prop entfernt
Konsequenz: existierende Waende verlieren ihren Section-Hatch beim
naechsten Regen. User definiert Section-Hatches jetzt auf Layer-Ebene.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
User: B/I/U Toggle-Buttons hinken hinterher oder zeigen invertiert.
Im WYSIWYG sieht alles richtig aus aber Rhino zeigt nur die LETZTE
gesetzte Schriftart fuer alles.
Fix 1 — B/I/U sync:
- selectionchange-Listener pollt jetzt queryCommandState fuer
bold/italic/underline und syncen das an Toolbar-State
- toggleBold/Italic/Underline machen nur noch exec() — kein manuelles
setBold(b => !b) mehr (war out-of-sync wenn execCommand wegen
fehlender Selection nicht griff)
- B/I/U-Button-Highlight reflektiert jetzt die echte Cursor-Position
Fix 2 — RTF nimmt nur letzte Schrift:
Reihenfolge im _commit war falsch. Vorher: RichText → TextHeight →
_apply_font → _apply_align. _apply_font setzt te.Font (Default-Font)
NACH dem RichText → schiesst die per-Run-Fonts in der RTF tot.
Neue Reihenfolge:
1. PlainText (Initial-Content)
2. TextHeight, Font, Align (Defaults fuer ALLES)
3. RichText (ueberschreibt Defaults pro Run)
Plus Font-Tabelle in _runs_to_rtf jetzt mit \fnil\fcharset0 Marker
(Rhinos RTF-Parser kann sonst font-Eintraege ignorieren und Default
verwenden).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Bug: Stile aendern nichts im Editor oder springen alle in eine
Zeile. Mit "Herumfummeln" partiell ge-fixed. Root-Causes:
1. Toolbar-Buttons stehlen Focus aus Editor → Selection futsch →
execCommand wirkt auf nichts. Fix: onMouseDown + preventDefault
auf B/I/U/Sup/Sub/Align (Pill akzeptiert jetzt onMouseDown prop).
2. Editor-div hat fontFamily/fontSize aus React-State → ueberschreibt
per-Span-Styles → alles sieht gleich aus. Fix: editor-div hat
statische Defaults (Helvetica 20px), per-Selection Styles wirken
ueber span-Wrapping (applyInlineStyleToSelection).
3. Newlines kollabieren (text springt auf eine Zeile). Fix:
white-space: pre-wrap auf editor-div.
4. Font/Size dropdowns: alter execCommand fontName war buggy. Neu:
applyInlineStyleToSelection('font-family', font) bzw. 'font-size'
wickelt die Selektion in ein <span style="..."> ein, neue Selection
liegt auf dem Span (Folge-Operationen wirken sauber).
5. Selection-change Event-Listener speichert die letzte Editor-Selection
in savedRangeRef. restoreSelection() vor jeder Operation stellt sie
wieder her — robust auch wenn der Focus zwischendurch weg war.
Backend (_runs_to_rtf): BASE_PX = base_size_m * 100 statt hardcoded 14.
Frontend rendert 1m = 100px, also entspricht base_size_m*100px dem
\\fs20 in RTF (= 1.0× TextEntity.TextHeight). _commit passes settings.
size mit, damit das Mapping stimmt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User: Doppelklick auf DOSSIER-Text oeffnet weiterhin Rhinos Editor.
Verschiedene Groessen im Editor erscheinen nicht in Rhino.
Doppelklick-Hook (rhino/text_editor.py):
- _DossierTextDoubleClickHook subklassiert Rhino.UI.MouseCallback.
OnMouseDoubleClick prueft selektierte TextEntities auf UserString
"dossier_text"="1" und cancelled das Event (= blockt Rhinos
Standard-TextEdit-Dialog), setzt sticky["dossier_pending_text_edit"]
mit der Obj-ID
- _on_idle_check_pending_edit (RhinoApp.Idle event): nimmt sticky-ID
auf naechstem Idle-Tick und ruft open_for_edit(obj) — defer noetig
weil Eto-Form aus MouseCallback heraus oeffnen Re-Entrancy macht
- _ensure_double_click_hook() installiert Hook + Idle-Handler einmalig
pro Rhino-Session (idempotent)
- startup.py ruft das jetzt direkt nach Modul-Load auf
Edit-Mode (open_for_edit):
- Liest aus bestehendem TextEntity die Settings (Font, Size, Bold,
Italic, Underline, Align) + PlainText
- Frame fuer Dialog-Positionierung aus BBox abgeleitet
- TextEditorBridge mit edit_obj_id + initial_text gestartet
- INIT-Payload um initialText + editMode erweitert
- COMMIT: bei edit_obj_id gesetzt → doc.Objects.Replace statt AddText.
Plane wird vom Original uebernommen wenn keine explizite Rotation,
damit der Text an seinem Platz bleibt
Frontend (TextEditorApp.jsx):
- Bei INIT mit initialText: editor.innerText wird damit befuellt
- htmlToRuns extrahiert font-size in Pixel pro Run (inline style oder
computed style != base)
- baseCtx _basePx aus computed style des Editor-divs
Size-Mapping (rhino/text_editor.py _runs_to_rtf):
- fontSizePx in Runs triggert non-trivial (RTF wird generiert)
- Pro Run: \fs in Halb-Punkten = 20 * (run_px / 14_base_px) round
- 14px = \fs20 (1.0× TextEntity.TextHeight)
- 21px = \fs30 (1.5×)
- 28px = \fs40 (2.0×)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Wunsch: vorgespeicherte Stile (Heading, Paragraph Helvetica/Georgia)
direkt im Editor anwendbar.
Backend (text_create.py):
- _DEFAULT_STYLES: 7 sinnvolle Architektur-Defaults — Titel (0.40m bold),
Heading 1 (0.30m bold), Heading 2 (0.20m bold), Paragraph Helvetica
(0.15m), Paragraph Georgia (0.15m Georgia), Notiz (0.10m italic),
Bildlegende (0.08m italic)
- list_styles: seedet die Defaults beim ersten Zugriff falls noch keine
Styles im Doc existieren (analog mass_style)
- Bestehende save_style/delete_style/apply_style funktionieren weiter
Backend (text_editor.py):
- INIT-Payload erweitert um styles[] (Liste aller verfuegbaren Stile
mit id/name/font/size/bold/italic/underline/align)
Frontend (TextEditorApp.jsx):
- Neuer Stil-Picker als erstes Dropdown in Toolbar-Row 1 (150px)
- Optionen: "— Stil wählen —" + alle verfuegbaren Stile
- onChange: applyStyle(style) — setzt Toolbar-State + appliziert via
execCommand auf die aktuelle Selektion im WYSIWYG-Editor (oder als
Default fuer kommendes Tippen wenn keine Selektion)
- queryCommandState-Check fuer Bold/Italic/Underline damit nur toggled
wird wenn nicht schon im gewuenschten State
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Wunsch: eigener WYSIWYG-Editor im React/Topbar-GUI-Stil. Topmost.
Verschiedene Schriftarten/-Dicken sichtbar im Editor selbst.
Neues Backend (rhino/text_editor.py):
- TextEditorBridge mit Frame-Daten im Konstruktor, INIT-Push mit
Settings + Font-Liste, COMMIT erstellt TextEntity, CANCEL schliesst
- open_with_frame(p1, p2, origin, width, height): oeffnet Satellite-
Window mit mode='text_editor' + topmost=True
- panel_base.open_satellite_window: neuer Parameter topmost (default
False) der form.Topmost setzt
text_create.create_text: ruft jetzt text_editor.open_with_frame nach
dem Frame-Pick. Eto-basierter _dossier_text_editor bleibt im Modul als
Fallback aber wird nicht mehr verwendet.
Neues Frontend (src/TextEditorApp.jsx, mode='text_editor'):
- Layout im DOSSIER-Topbar-Stil (dunkle Pills, accent on hover)
- Pill-Helper-Komponente fuer alle Toggle/Action-Buttons
- Dropdown-Helper fuer Font + Size
- Toolbar Row 1: Font-Dropdown | Size-Dropdown | Color-Picker | Layer-Reset
- Toolbar Row 2: B/I/U mit Material-Icons | L/C/R Align | x²/x₂ Sup/Sub
- Sonderzeichen-Palette: 41 Unicode-Symbole (Architektur/Math/Pfeile/
Auszeichnungen), Klick inserted am Cursor
- WYSIWYG-Editor: contentEditable div mit fontFamily=ausgewaehlt,
textAlign=ausgewaehlt — Format-Toolbar wirkt via document.execCommand
(bold/italic/underline/justifyLeft/Center/Right/superscript/subscript/
fontName/foreColor)
- "Einfuegen" sendet COMMIT mit text (innerText) + settings
- "Abbrechen" CANCEL → Bridge schliesst Form
Phase 1 Limitation: rendert in Rhino als PlainText mit den globalen
Settings (font/size/bold/italic/align/color) — verschiedene Schriftarten
INNERHALB des Texts sind im Editor sichtbar aber nicht im finalen
Rhino-TextEntity. Phase 2: HTML → Rhino RichText/RTF Mapping.
rhinoBridge.js: send() jetzt exportiert (war intern) damit
TextEditorApp generisch COMMIT/CANCEL senden kann.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Wunsch: Text-Editor war unfertig — keine Fonts sichtbar, Bold liess
sich nicht entfernen, Size soll Dropdown mit Eigene, Text-Stile noetig,
Unterstrichen + Links/Mitte/Rechts fehlten, schoenere Icons.
Backend (text_create.py):
- DEFAULTS erweitert um underline + align (left/center/right)
- _normalize() validiert Settings (align nur left/center/right)
- Text-Style-Preset-System analog mass_style:
- list_styles / save_style / delete_style / apply_style
- get_active_style_id / set_active_style_id
- doc.Strings["dossier_text_styles"] (JSON list mit id/name + settings)
- doc.Strings["dossier_text_style_active"]
- _apply_align(te, "left"|"center"|"right") setzt TextHorizontalAlignment
- apply_settings_to_selection + create_text rufen _apply_align mit auf
- read_selection_settings liest auch align
- available_fonts mit Fallback-Liste (Helvetica, Arial, Times, etc.) wenn
Rhino.DocObjects.Font.AvailableFontFaceNames leer ist
- underline: in Settings + Styles persistiert, NOCH NICHT visuell
appliziert (braucht TextEntity-RichText-API)
Backend (oberleiste.py):
- Neue Handler APPLY_TEXT_STYLE / SAVE_TEXT_STYLE / DELETE_TEXT_STYLE
- State liefert textStyles + textStyleActiveId
- textFonts jetzt bei jedem _send_state mitgeschickt (vorher one-shot mit
_fonts_sent flag — verlor sich nach Panel-Re-Mount und User sah keine
Fonts mehr)
Frontend (OberleisteApp):
- Text-Block komplett neu gelayoutet (3 Spalten Grid):
Reihe 1: [Style ▼] [Font ▼] [Size ▼]
Reihe 2: [B|I|U] [L|C|R] [+]
- Style-Dropdown mit Optionen "+ Speichern…" und "🗑 Aktiven loeschen"
- Size-Dropdown mit Preset-Werten (0.05/0.10/.../1.00 m) + "Eigene…"
→ toggle zu Custom-Number-Input bei "Eigene"-Auswahl
- B/I/U mit Material-Icons format_bold/italic/underlined statt B/I-Text
- L/C/R Alignment-Buttons mit format_align_left/center/right
- ToggleBtn-Helper-Komponente fuer alle 6 Toggles
- "+" Insert-Button bleibt klein (Icon size 14)
- Accent-Border auf allen Pills wenn Text selektiert (visuelles Feedback
"Aenderungen wirken auf Selektion")
- Bold/Italic/Underline lassen sich jetzt sauber togglen (waren als
proper Booleans serialisiert — vorher Bug evtl. durch fehlende Font-
Liste maskiert)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Probleme:
1. Floating Eto-Dialog erschien nicht nach GetPoint (Mac-Bug)
2. UI war zu klobig, sollte vectorworks-mässig kompakt sein, "+" als
kleines Icon-Symbol
3. Selektierten Text aendern war nicht moeglich
Fix 1 — Bug: _floating_input geloescht, ersetzt durch
_prompt_for_text() das Rhino.UI.Dialogs.ShowEditBox benutzt. Nativer
cross-platform Dialog ohne Eto-Modal-Bug. Workflow:
GetPoint → ShowEditBox → AddText. Funktioniert auf Mac.
Fix 2 — UI: Text-Block kompakt umgebaut.
Reihe 1: [Font ▼] | [Size m] (130 + 60 = 196px)
Reihe 2: [B][I][+] segmented pill (gleiche Breite wie Reihe 1)
"+" ist jetzt kleines add-icon (size 13), kein "+ Text" Label mehr.
Fix 3 — Edit-Selection: neue Funktionen in text_create.py:
- _selected_text_objects(doc) → Liste der selektierten TextEntities
- read_selection_settings(doc) → Settings der ersten Selektion
- apply_settings_to_selection(doc, patch) → wendet font/size/bold/italic
auf alle selektierten TextEntities an
oberleiste.SET_TEXT_SETTINGS handler appliziert die Aenderung jetzt
ZUSAETZLICH auf die Selection (wenn vorhanden) — UND speichert als
Default. State enthaelt textSelectionSettings, UI nutzt diese als
Anzeige-Werte wenn vorhanden (Werte spiegeln Selektion live).
Visual: Border-Color der Size/Segmented-Pill wird accent wenn Selection
aktiv ist (Hinweis dass Aenderungen auf Selektion wirken).
Sig-Update: textSelectionSettings + lastSetView in last_state_sig damit
State neu gepusht wird wenn sich Selection/View aendert.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User: 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>
User-Feedback:
1. View-Bars sind hoeher als andere Elemente auf der Seite
2. Active-Highlight bleibt auf Top haengen — andere Views leuchten nicht
3. Glitch: Klick auf Top → Bar zeigt weiterhin Perspektive aktiv
Fix 1 (Hoehe): Stat-Box Inhalts-Hoehe BAR_H*2+4 → BAR_H*2+6, der innere
Trennstrich-Gap 4 → 6. Damit visual 50 → 52 = identisch mit den 2-row-
Blocks (View, Preset, Massstab).
Fix 2 + 3 (Active-Highlight): Backend trackt `self._last_set_view` ←
gesetzt wenn handler in SET_VIEW erfolgreich war. Frontend matchView
prueft zuerst `state.lastSetView === v` — kein Race-Condition zwischen
ChangeProjection und Viewport-State-Lesen mehr.
Fallback auf Viewport-State-Detection wenn lastSetView noch null
(initial load). N/O/S/W kriegen jetzt auch Active-Highlight (vorher
hartcoded false).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User: 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>