UX-Verbesserung: User-Frage "muss ich nur das Element anwaehlen das ich
snappen moechte?" — jetzt ja.
Wenn nur 1 wand_axis in der Selection ist, sucht T-Join automatisch die
naechste andere Wand-Achse im Doc (innerhalb 1m) und snappt die selektierte
Wand auf jene. Die andere bleibt unangetastet — wie bei klassischem T-Stem
gegen Through-Wand.
2-Wand-Modus bleibt: dann werden GENAU die beiden selektierten betrachtet
(z.B. wenn 3+ Waende in der Nähe sind und User exakt eine andere meinen will).
Selection-Hint vereinfacht: nur noch warnen wenn 0 Wand-Achsen aber wand-
Objekte vorhanden (z.B. nur Volumen selektiert, wo Axis nicht mitkam).
User-Test: Bei T-Konfig waren Wand-Achsen 55cm auseinander (vermutlich
auf Outline statt Axis gesnappt). Mit 20cm tol fiel T-Join durch → L-Join
machte Tangent-Schnittpunkt (= L-Form falsch fuer T-Intent).
Fix: T-Join Snap-Radius von 20cm auf 1m. Generous genug fuer typische
Drift-Faelle (Outline-Snap statt Axis-Snap, ungefaehre Platzierung etc.),
aber tight genug damit absichtliche Lücken (>1m) nicht versehentlich
zugesnapped werden.
Plus: Hint wenn < 2 Wand-Achsen selektiert → User weiss explizit dass
GENAU 2 Wand selektiert sein muss.
User-Test: L-Join layered funktioniert (3/3 layers built). Aber T-Join laeuft
fuer layered immer in den L-Join-Fallback. Um zu sehen warum, jetzt fuer
jeden der 4 Endpunkt-Checks im T-Join:
- distance zur anderen Curve
- distance zur deren Endpunkten
- Reason warum verworfen (zu weit / nahe Endpunkt / schon snapped)
So koennen wir live im Log sehen ob T-Stem geometrisch zu weit weg ist
oder ob er an einem Endpunkt der Through-Wand landet (= L-Sache).
ROOT CAUSE: Wenn doc.Objects.Replace einer wand_axis ausgefuehrt wird (z.B.
nach L-Join von smart_join), prueft der Replace-Listener ob das neue Object
schon Meta hat. Wenn nicht, re-attached er via _attach_meta — aber die
wand-spezifischen Felder (wand_layered, wand_layers, wand_style_id,
wand_joint_rolle) wurden NICHT mit-uebergeben.
Effekt: layered Wand verlor wand_layered=True nach L-Join → naechster Regen
sieht is_layered=False → baut single SOLID Brep statt per-Layer-Breps →
"die gemerged walls sind dann leider solid walls".
Fix: Im Re-Attach-Aufruf die 4 wand-Felder mit weiterreichen aus meta.
Folge-Effekt: auch wand_style_id und wand_joint_rolle waren weg, was
Material-Lookup + Joint-Rolle-Override gebrochen hat. Beide jetzt fix.
Bug: in _walls_and_curves_from_sel + safety check + diagnostic wurde
"dossier_type" als UserString-Key gelesen, aber der echte Key (definiert
in elemente.py via _KEY_TYPE) ist "dossier_element_type".
Effekt: kein einziges Objekt wurde als wand_axis/wand_volume erkannt.
ALLES landete im "elif t == '':" Branch (= generic curves).
Solid L-Join funktionierte per ZUFALL: bei Solid-Wand (axis + outline +
volume) sind nur 1 Curve open (axis); outline ist closed rectangle. Bei
2 Solid-Waenden waren also 2 offene Curves in generic → L-Join fand die
2 vermeintlich generic Curves (= eigentlich Achsen).
Bei Layered scheiterte es: 2 Achsen + 2 Centerlines = 4 offene Curves im
generic. L-Join Bedingung "len(generic) == 2" nicht erfuellt → silent
return → Fallthrough zu _Join.
Fix: alle 4 Vorkommen auf "dossier_element_type" gefixt. Jetzt erkennt
smart_join Waende richtig, dedupliziert per wall_id, und T-Join/L-Join/
Safety-Check funktionieren wie geplant.
T-Junction-Detection (_detect_t_junction): pos_tol von 1mm auf 1cm erhoeht.
User-Feedback: bei manuellem Snap kann's leicht ein paar mm danebengehen,
1mm war zu eng. 1cm ist immer noch tight genug fuer Architektur-Workflow.
smart_join (dJoin): Safety-Check vor _Join-Fallback. Wenn IRGENDEINE
wand_axis in der Selection ist (auch zusaetzlich zu anderen Curves), wird
NICHT auf Standard-_Join gefallen — sonst kleistert _Join mehrere Wand-
Achsen zu einer Polyline zusammen, der Listener detektiert das als
"Source-Duplikat" und vergibt neue Wall-IDs → alle Meta-Verknuepfungen
brechen.
Stattdessen: Print-Meldung dass T-Join/L-Join nicht gegriffen hat. User
muss GENAU 2 Waende selektieren die verbunden werden sollen.
Bisher: bei T-Junction stoppen ALLE Layers des T-Stems uniform am Near-Face
der Through-Wand (uniformer T-Miter pro Wand).
Neu: per-Schicht Logik im _make_wand_layer_breps:
- T-Stem-Schicht mit Material das in Through-Wand auch vorkommt → Layer-Axis
extends um through_dicke/2 → Layer durchstoesst Through-Wand bis zur
Far-Face → visuell verbunden, kein sichtbarer Joint
- Schicht ohne Material-Match in Through-Wand → standard T-Miter, stoppt
am Near-Face
Beispiel "Aussenwand 30cm" (Beton/Daemmung/Putz) T auf gleicher Aussenwand:
alle 3 Schichten matchen → durchgehend gemerged.
Beispiel "Aussenwand 30cm" T auf "Beton solid 15cm":
- T-Stem Beton matched (Through-Solid hat Beton via Style) → durchstoesst
- T-Stem Daemmung/Putz: kein Match → stoppen am Through-Beton-Aussenkante
Implementation:
- _make_wand_layer_breps: per_layer_ext_start/end + per_layer_miter_start/end
- _t_junction_layer_overrides(doc, my_meta, through_meta, ...): baut die
per-Layer Overrides via Material-Set-Lookup. Solid through wird via
_wand_solid_material auf Style-Material aufgeloest.
- Regen-Pfad: t_junction_start/end speichern (oid+tan+dicke+ep+out_dir),
vor _make_wand_layer_breps die Overrides bauen.
Bisher konnte dJoin nur L-Verbindungen herstellen (zwei Endpunkte zum
Schnittpunkt der verlaengerten Tangenten ziehen). Neu auch T-Verbindungen:
_t_join_attempt: pro Endpunkt-Kombination wird der naechste Punkt auf der
ANDEREN Curve gesucht. Wenn distance < 20cm UND nicht nahe deren Endpunkt
(= waere L-Sache) → snap diesen Endpunkt exakt auf die Curve. Die andere
Curve bleibt unveraendert (= Through-Wand stays).
_run: T-Join wird ZUERST probiert (spezifischer), L-Join als Fallback.
UX: User selektiert 2 Waende die fast aber nicht ganz verbinden →
Cmd+J (dJoin) → System erkennt T- oder L-Konfig und snappt entsprechend.
Predictable + intentional, kein auto-snap-Magic mehr.
User-Feedback: bei vielen Waenden im Layout wurde unklar wann sich Wand-
Endpunkte automatisch verbinden und wann nicht. Auto-Snap auf Move/Mirror
machte das Verhalten unvorhersehbar.
Jetzt zurueck zum Standard-BIM-Workflow:
- Wand-Endpunkte verbinden sich NUR wenn User explizit Rhino's End/Mid-
Snap nutzt (oder das L-Join in dJoin)
- T-Junction-Detection bleibt 1mm-tight → praezise Geometrie erforderlich
- Joint-Rolle (auto/durchgehend/anstossend) regelt weiterhin wer am
geprueften T-Stoss durchgeht
Helper _snap_endpoint_to_other_wand_axis bleibt — fuer evtl. spaetere
explizite Connect-Befehle.
Neues UserString-Feld 'dossier_wand_joint_rolle' per wand_axis:
- 'auto' (default): bisherige Logik. Bei beidseitig auto entscheidet die
Style-Prio (hoehere = durchgehend)
- 'durchgehend': diese Wand ueberschreibt → ich gehe durch, T-Miter wird
NICHT auf mich angewendet
- 'anstossend': diese Wand stoppt immer am Joint, auch wenn ich Prio-haerter waere
T-Junction-Detection in regen ruft jetzt _wand_should_apply_t_miter:
- my.rolle entscheidet zuerst
- bei my=auto: through.rolle entscheidet
- bei beide=auto: Prio-Vergleich (hoehere Prio = durchgehend)
- Default: T-Miter applied (= ich stoppe am Through)
Frontend: neue Dropdown 'Joint' in WallProperties zwischen Stil und Aufbau:
- Auto (Prio entscheidet)
- Durchgehend
- Anstossend
Backend-Pipeline: _attach_meta + _read_meta um wand_joint_rolle erweitert,
state-JSON sendet 'jointRolle', _update_wall_body handhabt jointRolle-Patch.
Cluster-Dispatch: is_layered_meta Check zurueck — layered + non-linear (T,
Verzweigung) ist geometrisch sinnlos via per-Layer-Union (Schichten orthogonal
zwischen perpendikulaeren Waenden). Faellt zu Solo+Miter durch.
dWall-Prompt:
- Neue Option 'Aufbau' (Solid|Mehrschichtig)
- Stil-Liste gefiltert auf den gewaehlten Aufbau-Typ
- Beim Toggle: erster Style des neuen Typs uebernommen + dicke/referenz
- Default-Aufbau aus dem zuletzt verwendeten Style
Bug: nach _Delete einer Wand blieben die Hilfslinien (Centerline, Outline)
als Orphan-Curves stehen. _find_all_volumes filtert nur VOLUME_TYPES, in
denen Centerline/Outline nicht sind.
Fix: bei wand_axis-Delete-Event zusaetzlich alle wand_centerline/wand_outline
Curves mit derselben wall_id ID-Liste ergaenzen → nach 500ms cascade-delete
raeumt sie mit weg.
Layered Cluster + per-Layer BooleanUnion via _build_cluster_layered_breps
+ erweiterter _regen_cluster_anchor sind im selben commit drin (siehe
diff vor diesem Bugfix).
Bisher: bei jedem Project-Settings-Save wurden ALLE wand_axis im Doc regenert
(auch wenn nichts wand-relevantes geaendert wurde, z.B. nur ein Default-Wert
oder ein Library-Eintrag).
Jetzt: Diff der wand_styles + materials vor/nach Save. Sammle nur die Waende
die einen geaenderten Style nutzen ODER ein geaendertes Material (direkt via
Style oder als Schicht in layered Wand). Regen nur diese.
Plus: Joint-Cache-Batch-Flag waehrend des Regens setzen (war im sync-Pfad
sonst pro Regen invalidiert worden → unnoetiger Overhead).
Log zeigt jetzt z.B. "3 Waende regenert (1 Stile, 2 Materials geaendert)"
statt blind "N Waende regenert".
Beim install_listeners() werden dummy Curve.Offset, Extrusion.Create und
Brep.CreateBooleanUnion/Difference Aufrufe gemacht damit der lazy-loaded
native code geladen ist. Soll den First-Call-Lag bei der ersten echten
Wand-Operation reduzieren.
In der Praxis hilft das bei der Tech-Drawing-Failure nicht (das ist eine
Rhino-interne Limitation der Hidden-Line-Analyse die mit rapid brep
Delete+Add nicht klar kommt) — aber schadet auch nicht und kann andere
Cold-Start-Edge-Cases verbessern.
Bug: Beim Verbinden/Trennen zweier Waende via Move (= synchroner Replace-
Event-Pfad) lief jeder _regenerate_element-Call ohne den
_dossier_regen_batch_active Flag → jeder Regen invalidierte den Joint-Cache
neu (O(N²) Rebuilds) → Rhinos Technical-Drawing-Analyse choked →
"Switching all technical views to wireframe display".
Fix: Im post-Command Regen-Loop (line ~17285) den Flag setzen + Cache
einmal invalidieren bevor die affected_walls durchlaufen. Pattern analog
zur Idle-Batch-Logik die das schon hatte.
Outline + Centerline wieder aktiviert — waren nicht der Schuldige.
Die Outline-Curves lagen exakt auf z=0 wie die unteren Brep-Edges. Rhinos
Technical-Drawing Hidden-Line-Analyse choked nach jedem Regen mit Duplikat-
Linien → wireframe-Fallback.
- _regen_wall_lines.outline branch auskommentiert
- _migrate_strip_wand_outlines_once raeumt existierende wand_outline-Curves
beim naechsten Doc-Open weg
- Centerline (offset, dashed, locked) bleibt — kein Edge-Overlap
Wenn Outline-Visualisierung wieder gewollt: via Display-Conduit (visual-only,
keine Tech-Drawing-Interferenz).
Backend
- wand-State enthaelt jetzt styleId (aus _KEY_WAND_STYLE_ID)
- STATE-Payload sendet wandStyles (analog oeffStyles)
- _update_wall_body handhabt styleId-Patch: bei Stil-Wechsel uebernimmt die
dicke aus dem Stil (wenn nicht explizit im selben Patch ueberschrieben);
wand_style_id wird per _attach_meta auf die Achse persistiert
Frontend
- WallProperties bekommt wandStyles-Prop + zeigt Stil-Picker zwischen
Geschoss und Aufbau (nur wenn Stile vorhanden)
- Dropdown: "kein Stil" + alle definierten Stile mit (dicke, prio)
- PropertiesView + ElementeApp + ElementePropertiesApp propagieren wandStyles
Outline + Centerline
- Outline-Curves (geschlossenes Viereck) jetzt fuer ALLE Waende (auch
Cluster/Chain-Member) — in_cluster-Flag suppressed Outline nicht mehr
- Mode = Normal (statt Locked) damit ObjectColor (Petrol #5fa896) durchschlaegt
und doc.Objects.Replace bei Pure-Transform funktioniert
- Trade-off: User kann Hilfslinien greifen, aber jeder Axis-Regen schreibt sie
neu (selbst-korrigierend)
Pure-Transform Sync
- wand_centerline + wand_outline werden im Pure-Transform-Pfad explizit
mit-transformed (vorher nur SOURCE_TYPES + VOLUME_TYPES → Linien blieben
in alter Position haengen)
Selection Pairing
- wand_axis zurueck in _PAIRED_SOURCE_TYPES → Achsen-Klick selektiert
Outline + Centerline mit (alle "Referenzlinien" leuchten zusammen auf)
- _collect_partners special-case fuer wand_axis: NUR Hilfslinien als Partner,
NICHT das Volume (sonst wuerde das ganze Brep mit-aufleuchten)
- wand_volume Pairing wie bisher: Volume-Klick → Achsen + Centerlines +
Outlines aller Cluster-Members
Bisher griff der Schrittmass-Clamp nur bei treppe_art=='gerade'.
Fuer L-Treppen jetzt:
- gp2 (Eck-Klick): clampt erste Lauf-Laenge auf [1-Stufe-min, (n-1)-Stufen-max]
- gp3 (End-Klick): schaetzt N1 aus erster Lauf-Laenge, clampt zweiten Lauf
auf den S/A-konformen Bereich (mit cut_back kompensiert)
Volume-Fix _make_treppe_l_volume:
- cut_back am Eckpunkt war hardcoded half_b (= passt nur fuer Lage=mid).
Fuer Lage=links/rechts wird die FULL breite cut-back gebraucht — sonst
ueberlappen die Lauf-Volumen am Eckpunkt mit dem Podest falsch.
Fix: cut_back = half_b if mid else breite.
User-Feedback: L-Treppe bleibt 3-Punkt-Polyline (Start/Eck/End), Podest
ergibt sich aus dem Eckpunkt + Breite. Aber Referenz darf nie 'mid'
sein — sonst kollidieren die Laeufe am Eck. Constraint:
- _update_wall: bei treppe_art=='l' und tref=='mid' → 'links' erzwungen
- Frontend: REF_OPTIONS filtert 'mittig' raus wenn treppeArt=='l'
Dead-Code: _l_segments + _aussen_l_polygon + _lauflinie_l + _make_treppe_l_volume
behalten 4-Punkt-Handling als optionalen Pfad (gerade nicht erreicht weil
Creation immer 3-Punkt produziert) — schaden nicht, koennen spaeter
wieder aktiviert werden wenn Podest-als-Segment doch gewuenscht.
Schlafsession-Status: Trittmass-Lock + Lauflinie zentriert + Pfeile +
Cmd+Z + Pure-Transform fuer hidden Layer = alles in main.
_line_intersect_xy lieferte Z=0 was nicht zum Polygon-Z (=OKFF) passte,
PolylineCurve mit gemischten Z konnte fehlschlagen → 2D-Generierung
broken fuer L-Treppen. Fix: _at_z(p, fallback) Helper setzt Z explicit.
Plus try/except um die ganze Polygon-Konstruktion — Fallback auf
2-Rechteck-Variante bei Fehler.
Cmd+Z:
- _update_wall wrapped in BeginUndoRecord/EndUndoRecord — sodass
Property-Patches + Regen-Delete/Add als ein Undo-Schritt rueckgaengig
L-Treppe Aussenlinie:
- _aussen_l_polygon: sauberes 6-Punkt L-Polygon mit korrekt projizierten
Ecken (Outer + Inner via Linien-Schnitt der mid-versetzten Seiten)
- _aussen_l: nutzt Polygon wenn kein Cut, faellt sonst zurueck auf
per-Lauf Rechtecke mit Diagonal-Cut (wie bisher)
L-Treppe Lauflinie:
- ueber BEIDE Laeufe, mid-perp versetzt, Pfeil am Treppen-Ende
- Eck-Mitte als Linien-Schnitt der zwei versetzten Schaft-Linien →
sauberer Übergang auch bei Lage=links/rechts (kein Versatz an der Ecke)
- 'voll'-Pfeil-Style mit wide-Offsets relativ zur Lauflinie
Wendel-Treppe:
- _lauflinie_wendel: 'voll'-Pfeil-Style mit r_inner/r_outer-Spitzen
relativ zu r_mid (Radial-Offsets)
Trittmass-Lock auf alle Treppen-Arten:
- L: Beide Laeufe proportional skalieren (ratio = N*target_A / (L1+L2))
- Wendel: Sweep-Winkel anpassen (new_delta = sign × N*target_A/r_mid)
- Axis-Geometrie wird in-place via Replace ausgetauscht — Source moves
fliessen in regulären Regen-Pfad ein
Properties-Panel:
- Konsistentes 50px/1fr/14px Grid fuer alle Treppen-Rows
- Lage + Unten als Dropdown (lowercase Labels)
- Versatz: Dropdown (Geschoss-OKFF) oder eigenes Z mit Input + x-Button
- Ziel: gleich (Geschoss-Liste oder eigene Hoehe), Geschosse-Filter
excludes das Start-Geschoss
- Start-Dropdown filtert auf okff < Ziel-Z (kein hoeheres Geschoss als
Start waehlbar, beachtet auch eigene-Hoehe-Ziel)
- Stufen: Dropdown 2-40 (statt freie Eingabe), mit Lock nur S-konforme
Werte
- Dropdowns nutzen System-Font (statt mono)
- Ausgrenzung 'Aussenlinie'-Toggle (Aussenlinie immer an)
- Pfeil-Style-Dropdown unter Lauflinie-Checkbox: klassisch / gefuellt
(Solid-Hatch) / breit / voll (Spitzen bis Treppen-Aussenkanten)
Backend Treppe:
- Start-Z-Override via treppe_uk_over (m Offset relativ zu Geschoss-OKFF)
- 2D-Symbol bleibt auf OKFF (egal ob Versatz) — Symbol klebt am Boden
- Lauflinie-Schaft auf visuellen Treppen-Mittelpunkt versetzt
(bei Lage=links/rechts), nicht mehr auf der Referenz-Achse
- Trittmass-Lock: treppe_lock_s + target_S/A. Beim Aktivieren werden
S+A als Ziel gespeichert. Bei H-Change wird N=round(H/target_S)
recomputed + Axis-Laenge auf N*target_A angepasst (gerade Treppen)
- Bruchsymbol-Toggle aus: ganze Treppe ungesplittet zeichnen
(eff_cut_h=0 → kein Lower/Upper-Split)
- Treppen-Endpunkt-Marker (treppe_grips.py) — gruene Punkte an Start/
Ende der Lauflinie, beachtet treppe_art (Wendel: poly[1]/poly[2])
Verdoppelungs-Fix:
- _find_target_volume skipt treppe_2d_symbol explicit (sind 2D-Curves,
kein Volume). Vorher konnte Replace(curve, brep) fehlschlagen → das
echte Treppen-Brep blieb stehen + neues kam dazu → Duplikat
- _find_objects_by_wall_id mit HiddenObjects+LockedObjects-Iterator,
findet auch Objs auf hidden 3D-Layer
- Anti-Dup-Cleanup in _regenerate_element: bei mehreren treppe_volume
mit gleicher element_id → alle ausser dem ersten loeschen
State-Pipeline:
- geschosse-Liste enthaelt jetzt okff+hoehe (fuer Frontend-Constraints)
- Treppe-State neu: ukOver, arrowStyle, lockS, targetS, targetA
- Hidden-Source-Fallback in _send_state findet auch Treppen wenn der
3D-Layer aus ist (sodass Properties-Panel angezeigt wird)
Dimensionen-Panel:
- on_select + on_idle skippen waehrend Partnership-Cascade oder
User-Transform — kein Flicker mehr beim Drag
Andere:
- Wand-Polyline-Vertex-Grips (alle Vertices, nicht nur Enden)
- PopupMenu unterstuetzt _divider + checked-Items
- TREPPEN/RAEUME Layer-Migration auf Capital-Case
- selection-partnership tolerant: hidden Source wird trotzdem in die
Selection genommen (sonst kann Drag nicht durch Pure-Transform)
Frontend:
- 2D-Plansymbol pro Treppe (Tritte/Lauflinie/Aussenlinie/Bruchsymbol)
mit per-Treppe-Toggles in Properties-Panel
- 'Obere Stufen gestrichelt'-Toggle splittet Tritte/Aussenlinie an
Schnittebene; Lauflinie hat zwei Pfeile bei Bruch
- Wand-Polyline-Grips fuer alle Vertices (nicht nur Enden)
- PopupMenu unterstuetzt Divider + Checkbox-Items
Backend:
- Eigener Layer 41_Treppen_2D fuer Plansymbol, Layer-Default schwarz
- Aussenlinie-Polygone folgen der Bruch-Diagonale (kein Versatz mehr)
- Linetype-Fallback laedt Dashed bei Bedarf nach
- Tritten-immer-an (Toggle entfernt), Z auf Geschoss-OKFF
- TREPPEN/RAEUME Layer-Migration auf Capital-Case (Treppen/Raeume)
- Selection-Partnership: treppe_2d_symbol pairs in axis + volume
Pure-Transform fuer Treppen-Move:
- treppe_2d_symbol + treppe_volume in VOLUME_TYPES → cascade-Support
- Phase 1.5 Volume-only-Detection: wenn Source unbewegt aber Volumes
uniform translated → synthetisiere canonical aus Avg-Delta der
bewegten Volumes (unbewegte rausgefiltert sonst Verzerrung)
- Hidden-inclusive ObjectEnumerator in Snapshot + Apply-Loop damit
hidden treppe_axis auf 40_Treppen mit-transformiert wird
- Properties-Fallback im _send_state findet hidden Sources via
expliziter Iteration → Panel zeigt Treppe auch bei 3D-Layer aus
- Dimensionen-Panel skipt on_select/idle waehrend UT_ACTIVE oder
Partnership-Cascade → keine Flicker beim Drag mehr
User-Bug: Layout-Skip-Optimierung war zu aggressiv — Mac Rhino haelt die
Panel-Anordnung zwischen Sessions doch nicht im internen State, also
wurden Panels falsch platziert nach Quick-Restart. Skip-Logik raus, der
~3s _-WindowLayout-Apply laeuft wieder jedes Mal. Das ist OK weil der
Splash diese Wartezeit jetzt optisch abdeckt.
Splash verbessert:
- _try_borderless_mac(): direkter NSWindow-Zugriff via Eto.ControlObject
+ ObjC-Methoden (setStyleMask_, setOpaque_, setHasShadow_,
setBackgroundColor_, setMovableByWindowBackground_) — produziert
echten borderless Mac-Look wie der Launcher-Splash
- Form-BackgroundColor auf transparent damit das gradient des WebView-
HTMLs durchscheint (rounded petrol gradient mit weichem Verlauf)
- WebView selber transparenter Hintergrund
- Closeable/Minimizable/Maximizable/Resizable alle False
- [SPLASH] visible log fuer Debug-Sichtbarkeit
Verdeckt visuell die 3+ Sekunden Wartezeit beim Cold-Start (Panel-
Registration + WindowLayout-Apply). Stilistisch identisch zum
Launcher-Splash: petrol-gruener Verlauf mit "DOSSIER."-Logo, pulsierendem
Dot, animierter Progress-Bar.
Architektur:
- _startup_splash.py: zentrale show() / hide() Helpers
- Borderless Eto.Forms.Form (420x160), Topmost, kein Taskbar-Eintrag
- WebView mit Inline-HTML (gleicher Stil wie launcher/public/splash.html)
- Sticky-Key _dossier_startup_splash haelt die Form-Referenz
- Safety-Timeout 8s falls hide() vergessen wird
- startup.py _load_all: show() ganz am Anfang (bevor Imports laufen)
- oberleiste._on_ready: hide() via 200ms-Timer NACH window-layout-apply
(bzw. nach skip) — Layout-Animation ist auf Panels in Finalposition
kurz sichtbar bevor Splash verschwindet
Effekt: User sieht sofort einen schoenen Branded-Loading-Screen statt
3s grauer Rhino-UI mit halb-geladenen Panels.
Diagnose-Log zeigte: 3046 ms (= 93% der 3.3s Cold-Start-Zeit) gehen
allein in den _-WindowLayout RunScript-Call beim ersten oberleiste-
_on_ready. Rhino dockt dabei alle Panels neu — teuer.
Optimierung: Marker-File-basiertes Skip
- Nach erfolgreichem Apply: ~/Library/Application Support/
ch.gabrielevarano.Dossier/layout_marker.json mit Timestamp + Name
- Bei _on_ready: wenn Marker < 10 min alt UND derselbe Layout-Name →
skip (Rhino "remembers" die Panel-Positionen meistens noch im
internen State, nur unzuverlaessig zwischen LANGEN Pausen)
- Sonst (kalter Tag, langer Reboot, anderer Name): apply normal
Effekt fuer typischen Dev-Workflow (haeufige Quick-Restarts):
- Erster Restart heute: 3s Apply (wie bisher) + Marker
- Naechste Restarts < 10 min spaeter: ~0ms (skip)
- Nach Pause > 10 min: erneuter Apply
Helpers _is_layout_recently_applied + _mark_layout_applied. Marker
wird auch geschrieben wenn User manuell apply (z.B. via Settings-
Dialog oder Launcher pendingApplyLayout) → konsistent.
Falls Layout doch falsch ist nach Skip: Oberleiste-Settings →
"Jetzt anwenden"-Button erzwingt Apply.
Bisher: Wall-time 3.2s vs gemessene Arbeit 0.9s → 2.3s unmeasured.
Jetzt werden auch die bisher blinden Stellen getrackt:
- 9× Modul-Import (rhinopanel, oberleiste, ausschnitte, ...) als
phase="import", label=mod_id
- oberleiste display-mode-Ensure-Loop (4× DisplayModeDescription-Calls)
als phase="display_modes"
- _apply_window_layout RunScript-Sequenz als phase="window_layout"
- text_editor doppelklick-Hook + view-modes assign + unit-check als
post_init/hook Phases
Nach Reload sollte das Summary zeigen wo die 2.3s versickern → daraus
gezielte Optimierungen.
Download-Button (file_download Icon) neben den Expand/Collapse-Buttons
in der Toolbar. Klick → SaveFileDialog (Default 'sia_bilanz.csv') →
schreibt Excel-kompatible CSV.
Backend (elemente_uebersicht._export_bilanz):
- Wide-Format: Spalten = Kategorie + ein Geschoss + Total
- Zeilen: HNF, NNF, NF, VF, FF, NGF, GF, AGF, Räume (count), Personen
- Werte via _elm.compute_sia_bilanz fuer jeden Scope (gleiche Logik
wie Bilanz-Stempel + Uebersicht — single source of truth)
- Format: Semikolon-Separator + UTF-8 BOM + Komma als Dezimaltrenner
(Excel CH/DE-kompatibel, oeffnet ohne Umweg)
- Flaechen mit 2 Nachkommastellen, Raume/Personen als int
Architekten-Workflow: bei Schulen/Buero/Versammlungsstaetten muss die
Personenzahl pro Raum erfasst werden (SIA: m²/Person als Indikator).
Backend:
- UserString dossier_raum_personen (int)
- _attach_meta + _read_meta + state-emit
- _update_wall raum-Branch akzeptiert "personen" im Patch
- compute_sia_bilanz aggregiert personen-Summe ueber Scope
- Stempel: neue Show-Flag stempel_show_personen (default false) +
Bilanz-Renderer-Zeile "N Personen"
- Stempel-Stil-Field showPersonen mit dabei
Frontend (RaumProperties):
- Personen-Input (number, min 0) zwischen Funktion + Layout-Builder
- Nur sichtbar bei normalen Raeumen (nicht GF/AGF)
- Live-Anzeige "m²/Person" Suffix wenn area + personen > 0
→ User sieht sofort ob die Belegung sinnvoll ist (SIA-Vergleich)
Frontend (StempelProperties):
- Neuer Show-Toggle "Personen" (default off)
- Live-Vorschau zeigt Personen-Summe wenn aktiv + > 0
- _OFF_BY_DEFAULT Set generalisiert Default-Handling (showCount, showPersonen)
Doppelbelegung Code 60: alte Konvention "Plangrafik" steht im Weg fuer
die neuen Raum-Layer (RAEUME 60, GF 61, AGF 62). Wenn ein Doc beides
hat, fiel die Auto-Add-Logik in _find_ebene_sublayer_name aus → Raeume
landeten auf Rhino-Layer "60_RAEUME" der aber im Dossier-Ebenen-Panel
nie auftauchte (dort stand 60 = Plangrafik).
Fix in zwei Teilen:
1) Frontend-Default-Schema (App.jsx INITIAL_EBENEN):
- 60: RAEUME (neu, fuer HNF/NNF/VF/FF)
- 61: GF (Geschossflaeche)
- 62: AGF (Aussengeschossflaeche)
- 80: Plangrafik (verschoben von 60)
2) One-shot Migration in elemente._migrate_plangrafik_60_to_80_once():
- Detect: dossier_ebenen hat code=60 + name=Plangrafik
- Action: code 60 → 80, RAEUME/GF/AGF auf 60/61/62 hinzufuegen
- Rhino-Layer rename: alle "60_Plangrafik" Layer → "80_Plangrafik"
- build_layers + broadcast_state → Ebenen-Manager UI aktualisiert
- Sticky-Flag verhindert Re-Run
Plus kleinerer UX-Fix: Skala-Dropdown-Labels gekuerzt
("fix (m)" / "massstaeblich (mm)" statt langer Beschreibungen).
Geschossflaeche (GF) und Aussengeschossflaeche (AGF) sind reine
Flaechen-Ausweisungs-Outlines — keine Raeume mit Name/Nummer/Funktion.
Backend:
- Neue Layer-Helpers _layer_path_raum_gf (61_GF) und _layer_path_raum_agf
(62_AGF) — eigene Sublayer pro Geschoss, eigene Default-Farben
- _layer_path_for_raum_sia(doc, gname, sia): routet sia=gf → GF-Layer,
sia=agf → AGF-Layer, sonst RAEUME-Layer (HNF/NNF/VF/FF/leer)
- _regenerate_element_body raum_outline-Branch nutzt das Routing →
Source-Outline migriert automatisch auf den richtigen Layer
- _update_wall raum-Branch: bei SIA-Wechsel (z.B. HNF → GF) auch
Layer-Migration
- _make_raum_stamp_text: bei sia=gf/agf default-layout
override auf [["sia", "area"]] → Stempel zeigt nur "GF 234.5 m²" /
"AGF 18.0 m²" ohne Nummer/Name/Funktion
Frontend (RaumProperties):
- Conditional isFlaeche = sia in (gf, agf)
- Versteckt bei isFlaeche: Stil-Picker, Nummer-Input, Name-Input,
Funktion-Input, StempelLayoutBuilder
- Bleibt sichtbar: Geschoss, SIA-Tag-Selector, Fuellung, Rundung,
Skala-Modus, Flaeche/Umfang-Footer
- Info-Zeile zeigt bei isFlaeche: "Flaechen-Outline (GF) auf eigenem
Layer · Stempel zeigt nur GF + Flaeche"
Neuer Element-Type "stempel" — TextEntity die automatisch eine SIA-416
Bilanz aggregiert und im Plan platziert wird. Re-rendert sich live wenn
sich Raeume im Scope aendern.
Backend (elemente.py):
- Neue SIA-Tags: GF (Geschossflaeche), AGF (Aussengeschossflaeche)
mit eigenen Labels + Pastell-Farben in _SIA_COLORS_HEX
- "stempel" als SOURCE_TYPE; eigene UserStrings:
- stempel_scope: "total" | "geschoss:<gid>"
- stempel_txt_h, stempel_font, stempel_bold, stempel_italic
- compute_sia_bilanz(doc, scope): aggregiert nach SIA-Tags, liefert
HNF/NNF/VF/FF/GF/AGF + abgeleitet NF/NGF/count + Scope-Label
- _format_bilanz_lines: kompakte Stempel-Textzeilen ("HNF 120.5 m²"),
Trennlinien + nur Kategorien > 0
- _make_stempel_text: TextEntity-Builder mit Header "Nutzflächen · {Scope}"
- _regenerate_element_body "stempel"-Branch: in-place Replace mit
aktualisiertem Text (Position bleibt aus alter Geometrie)
- _regenerate_stempel_for_geschoss: regennt alle Stempel im selben
Geschoss + alle "total"-Stempel
- Auto-Cascade: raum_outline-Regen setzt sticky-marker; nach REGEN_BUSY-
Release wird _regenerate_stempel_for_geschoss aufgerufen
- _cmd_create_stempel: GetPoint im Viewport, layer = aktives Geschoss
Raum-Sublayer, default-Scope = aktives Geschoss
- _update_wall "stempel"-Branch: scope/txtH/font/bold/italic via patch
- elemente_uebersicht: SIA-Bilanz um gf/agf/ngf erweitert; "stempel"
als KIND-Eintrag
Frontend:
- createStempel-Bridge-Export
- "Stempel"-Pill-Button in der "Raeume"-PillGroup
- StempelProperties-Component: Scope-Dropdown (Total + alle Geschosse),
Bilanz-Vorschau mit Hervorhebung der NF (Accent-Farbe)
- KIND_META + RAUM_SIA_KINDS um GF/AGF erweitert
Workflow: Pill "Stempel" klicken → Punkt im Viewport → Stempel erscheint
mit Header "Nutzflächen · EG" + Bilanz. Properties: Scope auf Total
umstellen oder anderes Geschoss waehlen. Neue Raeume taggen mit SIA-
Tag → Stempel aktualisiert sich automatisch.
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).
DOSSIER-UI nimmt immer Meter entgegen — bisher wurden die Werte 1:1 als
Doc-Units verwendet, was bei Doc=Millimeter winzige Geometrie ergab
(0.25m getippt → 0.25mm Wand).
Storage-Konvention bleibt METER (UI-friendly). Konvertierung passiert
beim Geometrie-Bau:
- Neue Helpers _m2u / _u2m via Rhino.RhinoMath.UnitScale
- _regenerate_element_body normalisiert ALLE m-typischen Meta-Felder am
Eingang via _m2u — Geometrie-Code darunter bleibt unveraendert und
arbeitet in Doc-Units (funktioniert in jedem Unit-System)
- Lokaler _gs() Wrapper konvertiert Geschoss-OKFF + Hoehe → Doc-Units
- _resolve_uk_ok / _resolve_decke_z / _resolve_dach_base konvertieren
Geschoss-Heights aus JSON (m) → Doc-Units
- _resolve_raum_text_height_m masstab-Pfad: m-Result → Doc-Units
- _sync_raum_stamps_to_source: Stempel-TextHeight + Position-Delta sind
in Doc-Units, werden vor Storage via _u2m → m konvertiert
Effekt:
- Doc in Metern: kein sichtbarer Unterschied (UnitScale=1, no-op)
- Doc in Millimeter: 0.25 (m) wird zu 250 (mm) Wand → richtig dick
- State-Emit zum Frontend bleibt in m → UI konsistent