Das Dach liegt über der Schnittebene und wird von oben gesehen → das Pick-
Polygon der Traufe-Fläche trägt jetzt die Ansichts-Schraffur (viewHatchId)
der ÄUSSERSTEN Dachtyp-Schicht (Eindeckung), analog dem Decken-Poché in der
Aufsicht. Ohne Dachtyp/viewHatchId weiterhin weiss. +1 Test. 685/685 grün.
Die Decken-Sektion listet die Aussparungen (BBox-Masse je Loch) mit
Entfernen-Knopf; Anlegen über den Befehl 'Deckenloch'. CeilingInfo.openings,
host.onRemoveCeilingOpening + App-Routing. 684/684 grün.
Zwei Klicks (Ecke → Gegenecke, Live-Vorschau + Mass-HUD); die Aussparung wird
der Decke des aktiven Geschosses hinzugefügt, die das Rechteck vollständig
enthält (alle 4 Ecken im Umriss), sonst No-op. Aliase 'deckenloch'/
'aussparung', BIM-Ribbon-Eintrag mit eigenem Icon. Komplettiert die Decken-
Aussparungen (cdbef99) um den Zeichenweg. 684/684 grün.
Ceiling.openings (37f4fe8) wird jetzt gerendert. Kern: mergeHoles() fügt
Löcher über schmale Brücken (1e-4) in den Aussenring ein → EIN einfacher
Polygonring, paritätskorrekt für SVG-Fill, Scanline-Hatch, Ear-Clipping
(render3d) und Punkt-in-Polygon-Pick — kein Loch-Support in den Primitiven
nötig. Loch-Orientierung gegenläufig (signedArea), Sichtbarkeitstest für die
Brücke, mehrere Löcher iterativ.
- 2D (addCeilingPoche): Poché-Ring mit Löchern; Brückenkanten via
noStrokeEdges unsichtbar, Lochränder werden als Ringkanten gestrichen
(wand-footprint-geclippt wie der Aussenumriss)
- 3D (emitSlabs): derselbe Ring für alle Schichten; Klick ins Loch trifft
die Decke nicht (Pick paritätskorrekt)
- Ohne openings byte-identischer Codepfad (Regressionstests)
+11 Tests (Ring-Einfachheit, Shoelace-Flächen, 2-Loch-Fall, Regressionen).
684/684 grün. 3D visuell in Tauri zu prüfen.
Dachaufbauten sind jetzt im Ressourcen-Manager anleg-/editier-/löschbar
(RoofStylesTab auf dem LayeredStylesTab-Rumpf, Ressource roofTypes):
Schichtliste mit Bauteil/Dicke/Fugen-Linienstil, Löschen geschützt solange
ein Dach den Typ referenziert. Host-/App-Handler (patch/add/deleteRoofType)
+ i18n de/en. Vervollständigt die Dach-Schichtlogik (3a986ec) um die UI.
VW-Muster ('Klasse'/'Ebene'): der Objektinfo-Kopf zeigt statt des read-only
Kategorie-Codes zwei Dropdowns — Grafik-Kategorie und Zeichnungsebene
(Geschoss), beide direkt änderbar. Wirkt auf Wand/Decke/Dach/Treppe/Raum/
2D-Zeichnung/Stütze/Extrusion; Öffnungen ohne Ebenen-Dropdown (Ebene kommt
aus der Wirts-Wand), Extrusionen ohne Kategorie (kein categoryCode am Element).
- Selection.floorId (alle Builder), host.onSetSelectionCategory/-Level,
App-Routing je Elementart (Store-Actions bzw. setProject für Stütze/Extrusion)
- 2D-Zeichnungen auf Nicht-Geschoss-Ebenen behalten ihre Ebene in der Liste
673/673 grün.
Je Loch ein geschlossener Umriss innerhalb der Decken-Outline (additiv,
Alt-Projekte unverändert). Renderer-Konsum (2D-Poché-Loch + 3D-Slab-Loch)
folgt separat.
Der Umschalter war mit detailEnabled={planActive} in der Perspektive
deaktiviert — ein Relikt von vor der 3D-Verdrahtung (projectToModel3d bekommt
detail längst durchgereicht und re-pusht das Modell bei Änderung). Nutzer-
Report: 'Modelldarstellung funktioniert im 3D immer noch nicht'.
DoorType.glazingRatio wurde bisher von keinem Renderer gelesen (Designdoc).
Bei Glasblatt (leafStyle 'glas') füllt das Glas jetzt nur den oberen
glazingRatio-Anteil des Hauptfelds; darunter bleibt ein blindes Blattfeld
(z. B. Tür mit Oberglas). Fenster unberührt (glazedFraction = 1). +1 Test
(Halbglas: Unterkante angehoben, Oberkante am Sturz). 673/673 grün.
Bisher ein einziger overhang ringsum (Designdoc-Prio #2). Neu Roof.overhangGable
für den Ortgang (Giebelseite, entlang First); overhang gilt für die Traufe
(senkrecht zum First). Fehlt overhangGable, gilt ringsum overhang (rückwärts-
kompatibel). Geometrie mappt die Überstände je nach ridgeAxis auf die Outline-
Achsen. Panel: zweites Feld 'Überstand Ortgang' (ausser flach/zelt). +3 Tests.
672/672 grün.
DoorType.leafCount wurde bisher von keinem Renderer gelesen — eine zweiflügelige
Tür sah aus wie eine einflügelige (Designdoc-Prio #3).
- opening.ts: additive doorLeaves(wall, o, leafCount) — einflüglig ein Blatt
voller Breite, zweiflüglig zwei halbbreite Blätter an gegenüberliegenden
Pfosten, beide zur selben Seite (symmetrische Doppeltür). doorSymbol bleibt
unverändert (Parität).
- generatePlan: Tür-Zweig zeichnet je Blatt Linie + Schwenkbogen (leafCount aus
DoorType).
- toWalls3d: Tür-wingCount = leafCount → mittiger Anschlagpfosten (Stulp) im 3D.
- +1 Test (2 Blätter/Bögen, halber Radius). 669/669 grün.
roof.thickness war im 3D bislang ein totes Feld — Dächer waren einseitige
Flächen. emitRoofs baut jede Dachfläche jetzt als soliden Slab: Oberseite +
um thickness entlang der Flächennormalen (Newell) nach unten versetzte
Unterseite + umlaufende Seitenwände. Giebel bleiben dünne Endfüllung.
Basis für die kommende Dach-Schichtlogik. +1 Test (Unterseite unter der
Oberfläche); Walm-Zähltest auf Slab-Dreiecke aktualisiert. 667/667 grün.
Visuelle 3D-Abnahme in der Tauri-App durch den Nutzer.
Bisher gab es nur die Giebel-Mansarde (2-seitig). Neu über Roof.mansardType:
- 'giebel' (Default): Mansard-Satteldach, Giebel an den Enden (wie bisher)
- 'walm': allseitige Mansarde (Sockel + oberer Walm, kein Giebel) — 8 Flächen
- 'zelt': allseitige Mansarde zur flachen Spitze — 8 Flächen, kein First
Knicklage jetzt parametrierbar (Roof.mansardKneeRatio, Default 0.4 der halben
Spannweite). Dach-Panel: Mansard-Art-Dropdown + Knicklage-Feld (nur bei Mansarde).
+4 Geometrie-Tests (Ratio, Walm-/Zelt-Flächen/Grate).
Nebenbei: Fenster-Dialog-Labels klarer als 'Einbaulage ab / Abstand von Kante'
(Nutzer: wo sitzt das Fenster in der Aussparung, ab Innen-/Aussenkante + wieviel).
666/666 grün.
Öffenbare vs. feste Flügel sind jetzt unterscheidbar, und die Einbaulage
(Schichteinzug) wirkt auch im 2D-Grundriss.
2D (windowSymbol/generatePlan):
- Der reiche Pfad (typisiertes Fenster) zeichnet den Blendrahmen an der REALEN
Einbaulage (insetFace/insetFromFace, vorher ignoriert), je Flügel einen
Flügelrahmen (window-sash) nur wenn ÖFFENBAR, und das Glas darin. Feste
Flügel: Glas direkt im Blendrahmen.
- Die alte Diagonale quer über die Box (sah aus wie durchgestrichen) entfernt —
Öffenbarkeit wird jetzt über den Flügelrahmen ausgedrückt.
- Alt-Pfad (kein Typ) byte-identisch → kernel2d-Parität unberührt.
3D (frameMeshesForOpening/resolveOpeningFrame):
- Je öffenbarem Flügel ein Flügelrahmen-Ring (4 Riegel) im Blendrahmen (fein).
- Flügeltabelle konsistent aus explizitem sashes bzw. effektivem wingCount
(Instanz-Override wirkt jetzt auch auf Flügelrahmen); wingCount daraus abgeleitet.
Tests angepasst/ergänzt (fest vs. öffenbar, Zählungen). 663/663 grün.
emitOpeningShading zeichnet für Fenster mit shading.create eine helle 3D-Box
über der Öffnungs-OK, an der Aussenfläche ansetzend und um box.depth hinaus,
box.height hoch, box.width breit (0 ⇒ Öffnungsbreite). Nur mittel/fein.
Vervollständigt das Sonnenschutz-Feature (2D-Kontur war da, 3D-Box war in der
Studie 'später'). +3 Tests (Box-Primitive/Position, Leerfälle, grob).
3D visuell vom Nutzer in der Tauri-App zu prüfen. 662/662 grün.
Zwei vom Renderer bereits konsumierte Felder im Flügel/Sprossen-Panel
zugänglich gemacht + in der Live-Ansicht dargestellt:
- transomHeight (Oberlicht): 2D-Kämpferlinie im Grundriss + 3D-Oberlichtscheibe
waren schon da; Kämpferlinie jetzt auch in der Dialog-Vorschau
- mullionRows (Kämpfer-/Sprossenzeilen): 3D-Kämpfer bei 'fein'; waagrechte
Teiler in der Vorschau
Kein Renderer-Aufwand — reine UI-/Vorschau-Freischaltung. 659/659 grün.
Der 2D/3D-Renderer konsumiert die neuen Fenster-Modellfelder (VW-Studie §6):
- opening.ts: windowSymbol nimmt die Flügeltabelle (sashes); Trennlinien aus
Flügel-/Pfostenbreiten statt gleichmässiger wingCount-Teilung + Öffnungs-
andeutung (Diagonale zur Bandseite) je nicht-festem Flügel. Ohne sashes
exakt das bisherige Verhalten (Regressionssicherheit).
- generatePlan: glazingPanesOf steuert die Glaslinien-Anzahl (mittel/fein),
grob bleibt bei einer; gestrichelte Rollladenkasten-Kontur über der Öffnung.
- toWalls3d: glassPanesForOpening zeichnet glazingPanes dünne, in Wandrichtung
versetzte Scheiben mit Luftraum (Ein-/Zwei-/Dreifachverglasung); panes=1 =
exakt die alte Einzelscheibe.
Verhaltensänderung: window-standard (zweifach) zeigt 3D nun 2 Scheiben statt
einer — 2 Bestandstests entsprechend angepasst (+ Nicht-Überlappungs-Check).
+13 Primitiv-Tests (generatePlan.windowFields, toWalls3d). 659/659 grün.
Das ⚙ der Öffnungs-Sektion öffnet neu einen dedizierten Dialog (VW-Studie §3)
statt in den Ressourcen-Tab zu springen. Drei Zonen: Kategorie-Sidebar,
Parameter-Panel, Live-2D-Frontalansicht (aus den aktuellen Werten gezeichnet:
Rahmen/Flügel/Öffnungslinien/Verglasung/Rollladenkasten).
- OpeningEditorDialog.tsx: Kategorien Basis/Grösse/Rahmen/Flügel/Sonnenschutz
(Fenster) bzw. Türblatt (Tür); Flügeltabelle (Flügel/Pfosten + Öffnungsart +
Anschlag je Zeile); Stil-Leiste mit Stilwahl + 'Als Stil speichern …'
- App: openingEditorId-State, Dialog-Render, saveOpeningStyle (klont aktuellen
Typ als neuen benannten WindowType/DoorType und weist ihn zu)
- host.onOpenOpeningEditor + OpeningInfo.id für den ⚙-Sprung
- Studie docs/design/window-editor-vectorworks-study.md
- Dialog-CSS (.oed-*) + i18n (de/en)
Renderer-Konsum der neuen Felder (sashes/glazingPanes/shading in 2D/3D) folgt separat.
Gewählte Dächer bekommen im wgpu-Viewport Ziehgriffe wie Wände/Decken:
Eckpunkte (achsparalleles Resize, Gegenecke fix), Verschiebe-Griff im
Schwerpunkt und ein First-Griff am höchsten Dachpunkt — vertikales Ziehen
rechnet aus der neuen First-Höhe die Dachneigung (behält die Firstlage).
- projectSlice: moveRoofGrip/moveRoofBy (coalescing pro Dach)
- Wasm3DViewport: roofpitch-Griff (Render + vertikaler Drag auf Apex-Achse)
- App: edit3dRoofs + roofId-Routing in onEdit3dVertex/Body + onEdit3dRoofPitch
- Viewport3D: editRoofs/onEditRoofPitch durchgereicht
- Tests: roofGrips.test.ts (Resize/Verschieben/Coalescing)
Das Objekt-Info-Panel zeigte die Live-Messwerte nie an: `measurement` lag im
memoisierten `baseHost`, dessen Dep-Liste `draft` bewusst NICHT enthält (sonst
würde der ganze Host bei jeder Mausbewegung neu gebaut) → der Wert blieb auf
dem Anfangswert (null) eingefroren. Fix: `measurement` nicht mehr im Memo,
sondern in `hostWithMode` bei jedem Render frisch injiziert (draft?.measure).
tsc + vitest grün.
Bisher liess sich der Dach-Grundriss nach dem Platzieren nicht mehr in der
Grösse ändern. RoofSection bekommt Breite/Tiefe-Felder, die das Umriss-
Rechteck neu bilden (untere/linke Ecke bleibt fix). RoofInfo trägt
width/depth/minX/minY. tsc + vitest grün.
selectionHighlightLines bekommt roofIds und emittiert die Kanten aller
Dachflächen + Giebel (roofGeometry, Modell→World) als Akzent-Draht — ein
selektiertes Dach leuchtet jetzt im wgpu-Viewport wie Wand/Decke. App reicht
selectedRoofIds durch. +1 Test. tsc + vitest grün.
Ein selektiertes Dach wird jetzt im Grundriss mit der Akzentkontur markiert
(highlightRoofPolys über das Traufe-Pick-Polygon), analog Decke/Raum —
selectedRoofIds von App durch die Content-Sichten an PlanView durchgereicht.
tsc + vitest 642 grün.
- RoofSection bekommt ein Traufhöhe-Feld (Roof.baseElevation, absolutes Z) —
z. B. um das Dach für einen Kniestock über die Geschoss-Oberkante zu heben.
RoofInfo.baseElevation trägt den aufgelösten Wert (Override oder Default).
- Dach-Auswahl wird jetzt an ALLEN Selektions-Reset-Stellen mitgeleert
(Kontextmenü-/Ebenen-Operationen), nicht nur bei Klick/Geschosswechsel/
Marquee/Delete — kein Geister-Dach mehr in der Auswahl.
tsc + vitest grün.
Nutzer-Feedback „Fenster im 2D nicht korrekt / im 3D zu schwach":
- 2D: das Rahmenband (opening-frame) wurde beim Fenster ZUSÄTZLICH zum
windowSymbol-Rahmen gezeichnet → doppeltes, verschachteltes Rechteck.
Das Band gilt jetzt nur noch für Türen (deren Symbol nur Blatt+Bogen hat);
Fenster nutzen allein windowSymbol (Rahmen + Glaslinien). Oberlicht-Andeutung
(opening-transom) bleibt für beide.
- 3D: die Scheibe sass mittig tief im Reveal und wirkte schwach; sie sitzt
jetzt knapp (3 cm) hinter der Aussenfläche → klar sichtbar, Laibung zeigt
nach innen.
Tests angepasst (Band jetzt tür-spezifisch, Einzug am Tür-Zargenband geprüft).
tsc + vitest 640 grün.
Nachzügler-Fix zum Text-Styling-Test: das inline-gespreizte marks-Feld
verengte die Drawing2DGeom-Union und liess tsc meckern; ein Helfer baut das
Text-Drawing typkorrekt. Reine Testdatei.
Roof-Element (rechteckiger Grundriss + Form/Neigung/Überstand/Firstrichtung)
und Roof/RoofShape im Modell (Project.roofs). Reine Geometrie geometry/roof.ts:
roofGeometry(roof, eavesZ) liefert Grundriss-Linien (Traufe/First/Grat/Knick)
UND die 3D-Dachflächen (+ Giebel) für alle sechs Formen — gerechnet auf der
Bounding-Box (First entlang X/Y, „y" per Transponierung). roofBaseElevation
löst die Traufhöhe auf (Geschoss-Oberkante als Default).
+9 Tests (Firsthöhe/-lage, Flächen-/Grat-/Knick-Zahl je Form, Transponierung,
Überstand). Reine Modell-/Datenschicht; 2D/3D/Werkzeug folgen. tsc + vitest grün.
Frisch mit dem Fenster-/Tür-Werkzeug platzierte Öffnungen erhielten keinen
typeId → resolveOpeningFrame lieferte null → weder 2D-Rahmenband noch
3D-Rahmen/Laibung wurden gezeichnet (nur Loch + flache Scheibe). Das war
der Grund, warum die vertiefte Fenster-/Tür-Darstellung „im 3D gar nicht
so aussah". Fix: appendOpening weist den ersten Fenster-/Türtyp der
Bibliothek zu (Fenster→windowTypes[0], Tür→doorTypes[0]); ohne Bibliothek
bleibt das typlose Inline-Verhalten.
Nutzer-Feedback:
- Fenster wirkten im 3D flach: die Rahmen-Einbautiefe wurde fälschlich aus
frameThickness (Profil-Breite, ~6 cm) statt aus der Wanddicke bestimmt.
Jetzt füllt der Rahmen die VOLLE Wanddicke (tiefe Laibung), die Scheibe
ist eine dünne Tafel mittig darin → plastische Fenster mit sichtbarer
Laibung. frameDepth übersteuert weiterhin.
- Detailgrad grob/mittel/fein wirkt jetzt auch im 3D: grob = nur Loch +
Scheibe, mittel = Rahmen (+Oberlicht-Kämpfer), fein = zusätzlich
Flügel-Mittelpfosten/Kämpfer-Sprossen. Durchgereicht App → Viewport3D →
Wasm3DViewport → useWasm3dRenderer → projectToModel3d({detail}).
- Fenster↔Tür-Umschalter im Objekt-Info entfernt (nur noch Anzeige): die
Gattung wird beim Platzieren festgelegt, nicht nachträglich getauscht.
+1 Test (Detailgrad-Gating grob/mittel/fein). tsc + vitest grün.
Discoverability für die vertieften Bauteil-Typen: neben dem Typ-Dropdown
einer gewählten Öffnung öffnet ein ⚙-Knopf das Ressourcen-Fenster direkt
beim passenden Typeditor-Tab (Tür→doorStyles, Fenster→windowStyles).
- ResourceManager: initialTab-Prop (springt auch bei bereits offenem
Fenster auf den angeforderten Tab).
- host.onEditOpeningType(kind) + App-Wiring (resourcesTab-State).
tsc + vitest 620 grün.
Gewählte Decken bekommen im wgpu-3D-Viewport editierbare Griffe — analog
Wand/2D-Element:
- Eckpunkt-Griffe (Umriss-Vertices) → moveCeilingGrip.
- Je Seite ein Kanten-Mittelpunkt-Griff (rautenförmig, eigene Farbe) →
moveCeilingEdge (Nutzer-Wunsch „an allen seiten auch ein punkt").
- Verschiebe-Griff im Schwerpunkt → moveCeilingBy.
Alle drei Store-Actions existierten bereits (2D-Griffe); neu ist nur die
3D-Griffgeometrie (computeGrips), das Kanten-Drag (GripDrag „edge") und die
Prop-Verdrahtung Wasm3DViewport ← Viewport3D ← App (editCeilings/onEditEdge,
Griff-Ziel um ceilingId erweitert). Die three.js-Sicht bleibt unverändert
(Default ist wgpu). tsc + vitest 620 grün.
Die Text-Formatier-Gruppe der Oberleiste formatierte bisher nur den
Raum-Stempel. Jetzt ist auch ein selektierter Freitext/Textspalten-Element
(Drawing2D shape "text") ein Formatier-Ziel:
- Neues optionales Feld Drawing2D-Text.marks (Marks) für einheitliche
Formatierung (Schriftfamilie, fett/kursiv/unterstrichen, Farbe).
- App.textTarget adaptiert den Text über docFromText/plainText an die
Rich-Text-Leiste; die GRÖSSE bleibt bewusst über die Modell-Höhe (Meter),
nicht über pt (massstabslos wäre falsch) — sizePt wird verworfen.
- generatePlan/PlanView rendern die Marks (fontFamily/-weight/-style/
textDecoration); marks.color hat Vorrang vor der Kategorie-Farbe.
+3 Tests. tsc + vitest grün.
Bei mehrschichtigen Wänden lässt sich neben Aussen/Mitte/Innen jetzt auch
eine SCHICHTTRENNLINIE (Fuge zwischen zwei Schichten) als Referenzlinie
wählen — z. B. die Achse auf die Innenkante der Aussenschale legen.
- Neues Feld Wall.referenceOffset (freier Achsversatz entlang +n); wenn
gesetzt, übersteuert es referenceLine. wallReferenceOffset bleibt die
EINE Quelle → Grundriss, Schnitt und 3D erben das Verhalten automatisch.
- Object-Info-Panel: das Referenzlinien-Dropdown listet zusätzlich je
interne Fuge einen Eintrag (Kürzel der angrenzenden Schichten); Auswahl
setzt den Versatz, Aussen/Mitte/Innen löscht ihn wieder.
- selectionInfo berechnet die Fugen-Versätze (kumulierte Schichtdicken).
+3 Tests. tsc + vitest grün.
Setzt die vertieften Tür-/Fensterparameter im Grundriss-Schnitt um:
- opening-frame: Rahmenprofil als Band der Breite frameWidth in der
Laibung; Blockrahmen (Tür) als kräftigeres, vor die Laibung gesetztes
Profil, Zarge als schmales Laibungsband.
- Schichteinzug: die gewählte Fläche (aussen/innen) wird um insetFromFace
nach innen versetzt (Nahkante wandert, Fernkante bleibt).
- opening-transom: gestrichelte Überkopf-Linie über der Öffnung, wenn
transomHeight > 0.
Typlose Öffnungen zeichnen unverändert (Rückwärtskompatibilität getestet).
+5 Tests. tsc sauber, vitest grün.
Das Mess-Werkzeug wird von der reinen Strecke zum Polygonzug:
- Jeder Klick setzt einen weiteren Stützpunkt; angezeigt werden letztes
Segment (Länge · Winkel), aufsummierte Pfadlänge und — ab 3 Ecken —
die umschlossene Fläche (Gauss).
- Die Messwerte erscheinen dauerhaft im Objekt-Info-Panel (neuer
Abschnitt „Messung"), nicht nur flüchtig am Cursor-HUD. Kanal:
ToolDraft.measure → PanelHost.measurement → ObjectInfoPanel.
- Rechtsklick/Enter beendet den aktuellen Pfad und armiert sofort einen
neuen (mehrere Messungen nacheinander); Rechtsklick auf leerem Pfad
bzw. Esc beendet das Werkzeug.
+4 Tests (Segment/Winkel, Summe/Fläche, Ketten-Verhalten). tsc sauber.