Files
DOSSIER-STANDALONE/docs/design/window-editor-vectorworks-study.md
T
karim c734802555 Fenster-/Tür-Einstellungsdialog: reicher Editor mit Live-Ansicht + Stil speichern
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.
2026-07-10 01:13:15 +02:00

27 KiB
Raw Blame History

Fenster-/Tür-Editor — Studie & Designdokument (Referenz: Vectorworks „Fenster bearbeiten")

Status: Studie/Entwurf (KEIN Code). Ziel: den heute als „mega mager" empfundenen Fenster-/Tür-Editor zu einem eigenständigen, reichen Einstellungs-Dialog mit Kategorie-Sidebar, Live-Vorschau (2D + 3D) und „Als Stil speichern" ausbauen. Diese Datei ordnet die Vectorworks-Referenz dem bestehenden DOSSIER-Modell zu und schlägt einen realistischen, phasierten Plan vor.

Konvention: Bezeichner englisch, UI-Text/Kommentare deutsch. Meter als Grundmaß.


1. Executive Summary

Was ein guter DOSSIER-Fenster-/Tür-Editor sein sollte. Ein eigener modaler Dialog „Fenster-/Tür-Einstellungen" — nicht die heutige, in den Ressourcen-Manager eingebettete Formularspalte (WindowStylesTab/DoorStylesTab in src/ui/ResourceManager.tsx), die pro Feld nur eine FieldRow zeigt und keinerlei Vorschau bietet. Der neue Dialog hat drei Zonen (wie Vectorworks):

  1. Kategorie-Sidebar links (Basis, Größe/Position, Rahmen, Flügel/Sprossen, Oberlicht/Unterlicht, Laibung/Bank, Sonnenschutz/Rollladen, Attribute/Darstellung, Detaillierung).
  2. Parameter-Panel in der Mitte (die Controls der gewählten Kategorie).
  3. Live-Vorschau rechts: eine 2D-Plan-Vorschau (aus generatePlan()) und eine 3D-Ansicht/Elevation (aus projectToModel3d()), beide sofort aktualisiert.

Die eine wichtigste strukturelle Änderung. Der Editier-Primärort wandert vom Ressourcen-Tab in einen dedizierten OpeningEditorDialog, der TYP-Parameter (wiederverwendbarer Stil = WindowType/DoorType) und INSTANZ-Parameter (dieses Opening) im selben Fenster editiert und oben eine Stil-Leiste trägt: Stil: [Dropdown] · Fenster speichern… (aktuelle Konfiguration als neuen benannten WindowType/DoorType ablegen) · Einstellungen zurücksetzen… (auf den Stil zurückfallen). Das ⚙ in ObjectInfoPanel.OpeningSection (src/panels/ObjectInfoPanel.tsx:731) öffnet künftig DIESEN Dialog statt den Ressourcen-Manager-Tab.

Begriffsklärung. Der Nutzer-Ausdruck „als Wandstil speichern" ist ein Versprecher — gemeint ist „als Fensterstil/Türstil (Bauteilstil) speichern", also ein neuer Eintrag in project.windowTypes bzw. project.doorTypes, analog WallType/CeilingType/StairType. Es entsteht KEIN neuer Wandtyp.

Warum das der Hebel ist. Alle geplante Tiefe (mehrflügelig, Sprossenraster, Rollladen, Bank/Nische, Ober-/Unterlicht, Verglasungsanzahl) braucht (a) mehr Felder auf WindowType/DoorType und (b) eine UI, die sie ohne Formularwust zeigt und deren Wirkung sofort sichtbar macht. Ohne Live-Vorschau bleibt ein reicher Parametersatz unbenutzbar. Erst der Dialog macht die Tiefe zugänglich; die Modellfelder allein (Abschnitt 4) reichen nicht.


2. Vollständige Mapping-Tabelle (VW-Referenz → DOSSIER)

Legende — Status: vorhanden · ~ teilweise (Feld existiert, Renderer liest es nicht ODER nur grob) · fehlt. Ebene: T = Typ (Stil, wiederverwendbar, auf WindowType/DoorType) · I = Instanz (auf Opening). Priorität P0 (erste reiche Scheibe) … P3 (VW-Ballast). Aufwand grob in Personentagen (PT).

Wichtiger Ist-Befund aus dem Code (Renderer-Konsum-Lücken):

  • WindowType.glazing (einfach/zweifach/dreifach) ist editierbar, wird aber von KEINEM Renderer gelesen. 3D zeichnet stets EINE Scheibe (glassPanesForOpening in src/plan/toWalls3d.ts:1965 ignoriert glazing); 2D leitet die Glaslinien- Anzahl allein aus DetailLevel ab (addOpeningSymbol in src/plan/generatePlan.ts:2214, glassCount = detail==="fein" ? 2 : 1).
  • WindowType.kind/DoorType.kind (dreh/kipp/drehkipp/fest/schiebe …) schlagen sich NICHT in 2D/3D-Geometrie nieder.
  • DoorType.leafCount (1/2), leafStyle/glazingRatio (außer leafStyle==="glas" → 3D-Verglasung an) sind ungenutzt.
  • WindowType.sillBoard ungenutzt; DoorType.thresholdhasSill genutzt.
  • 3D-Mittelpfosten/Kämpfer und Sprossen werden nur bei detail==="fein" emittiert (frameMeshesForOpening in src/plan/toWalls3d.ts:1902, ab :1938).

2.1 Basiseinstellungen

VW-Control Status DOSSIER-Feld (Vorschlag) 2D-Wirkung 3D-Wirkung Ebene Prio Aufwand
Fensterart („Für normale Wand") WindowType.wallKind?: "normal"|"eck" (später) T P3 0.5
Öffnungsart (Dreh/Kipp/…) ~ WindowType.kind existiert, kein Render Öffnungssymbol/Öffnungslinien Öffnungslinien 3D T P1 2
Einfügepunkt längs (Mitte/…) Opening.position + Bezugspunkt in ObjectInfoPanel Position im Plan Position I
Einfügepunkt quer (Fensterseite außen/…) ~ insetFromFace/insetFace (T) Band-Lage Rahmen-Normalenlage (resolveFrameNormalRange :1803) T P1 0.5
Versatz im Fassadenmodul — (Fassadensystem fehlt in DOSSIER) P3
Laibung (wählen) ~ insetFromFace deckt Teil ab Laibungsstriche (fein) Laibungstiefe T P2 1
Klasse ~ Opening.categoryCode (LayerCategory) Farbe/Strich I
Darstellung höchste Detaillierung Opening.detailLevel + Ansichts-DetailLevel Symbolstufe Meshstufe I
Eigenes Symbol verwenden WindowType.symbolId? (Drawing2D-Ref) Symbol-Override T P3 3

2.2 Fenstergröße & Bemaßung

VW-Control Status DOSSIER-Feld 2D 3D Ebene Prio Aufwand
Fensterbreite (Wert) Opening.width (+ defaultWidth T) I/T
Fensterhöhe (Wert) Opening.height (+ defaultHeight T) I/T
Bezug B1..B5 / H1..H7 (Roh-/Fertigmaß) WindowType.dimRef?: {...} Bemaßungsschema T P3 3+
„Bemaßung automatisch" (außen/innen) — (DOSSIER hat noch keine parametrische Öffnungs-Bemaßung) Maßketten T P3 5+

Der ganze VW-Maßketten-/Bezugsapparat (B1..B5, H1..H7, Roh-/Fertigmaß-Umschaltung) ist P3/„nicht bauen" — siehe Abschnitt 5. DOSSIER trägt lichte Maße (width/height), das genügt.

2.3 Höhe Brüstung/Sturz

VW-Control Status DOSSIER-Feld 2D 3D Ebene Prio Aufwand
Position definieren durch (Brüstungshöhe/…) Opening.sillHeight (+ defaultSillHeight T) vertikale Lage (openingVerticalExtent) I
Abstand Brüstungshöhe (Wert) Opening.sillHeight I
„bezieht sich auf" (Wandaußenseite/…) — (immer Wand-UK-relativ) P3
„auf Ebenenbasishöhe" ~ Wand-UK ergibt sich aus Geschoss (baseElevation)

2.4 Rahmenwerte

VW-Control Status DOSSIER-Feld 2D 3D Ebene Prio Aufwand
Rahmenbreiten (4 Kanten, asymmetrisch) ~ heute nur frameWidth (eine Zahl) → WindowType.frameWidths?: {left,right,top,bottom} Rahmenkontur (windowSymbol/addOpeningFrameBand) Rahmen-Boxen (frameMeshesForOpening) T P2 2
Symmetrisch-Toggle WindowType.frameSymmetric?: boolean T P2 0.5
Flügel mittig / Versatz WindowType.sashOffset?: number Aufschlag-Linie Flügel-Box-Lage T P2 1
Rahmenstärke quer zur Wand frameThickness / frameDepth (→ resolveAcrossWallDepth :1780) Rahmentiefe T
Schnitt-/Außenansicht-Rahmenbreiten ~ von frameWidth mitgezeichnet Bandbreite T P2 1

2.5 Flügeleinteilung (KERN-Tiefe — höchster Wert)

VW-Control Status DOSSIER-Feld 2D 3D Ebene Prio Aufwand
Flügeltabelle (Nr/Typ/Breite/Anschlag/…) ~ wingCount (Zahl) → neu WindowType.sashes: SashDef[] (siehe §4) Pfostenlinien (mullionLines) heute nur gleichmäßig Pfosten-Boxen nur gleichmäßig T P0 4
Typ Flügel | Pfosten SashDef.kind: "fluegel"|"pfosten" Pfostenlage frei Pfosten frei T P0 (inkl.)
Aut. Breite / Flügelbreite ~ SashDef.autoWidth/width (heute nur gleichmäßig) Teilungslage Teilungslage T P1 1
Pfostenbreite / „alle gleich" ~ SashDef.postWidth, WindowType.uniformPosts Pfostendicke Pfostenbox-Breite T P1 1
Anschlag (Drehbar links/Drehkipp rechts) SashDef.opening: OpeningKind, SashDef.hingeSide: "left"|"right" Öffnungslinien/Pfeil je Flügel Öffnungslinien 3D T P0 2
Aufschlag (Abstand zu Rahmen) SashDef.rebate?: number Aufschlag-Linie T P2 0.5
Winkel / 3D-Öffnung SashDef.openAngle?: number Flügel gekippt/offen T P2 1.5
Griffart SashDef.handle?: HandleKind Griff-Mesh (§2.7) T P2 1
Kämpfer-Zeilen (horizontale Teilung) ~ mullionRows existiert Querlinien Kämpfer-Boxen (fein) T P1

Dies ist die wertvollste Lücke: VW modelliert je Flügel Typ, Breite, Öffnungsrichtung, Anschlag, Griff. DOSSIER hat nur eine gleichmäßige wingCount. Die SashDef[]-Tabelle (§4, Phase P0/P1) ist der zentrale Ausbau.

2.6 Laibungsverkleidung · Form · Ober-/Unterlicht · Nische/Bank

VW-Control Status DOSSIER-Feld 2D 3D Ebene Prio Aufwand
Laibungsverkleidung erstellen WindowType.reveal?: {create, depth, thickness} Laibungsband Laibungs-Boxen T P2 1.5
Form (Eckig/Schräg/Spitz/Rund) WindowType.headShape?: HeadShape + Eckmaße Öffnungsumriss-Form Bogen-/Schräg-Mesh T P2 4
Oberlicht erstellen + Höhe/Rahmen/Sprossen ~ transomHeight existiert (feste Scheibe) → WindowType.transom?: {height, frame, grid} Kämpferlinie + Feld zweite Scheibe (glassPanesForOpening :1988) T P1 2
Unterlicht (unteres festes Feld) WindowType.underlight?: {height, frame, grid} Feld Scheibe unten T P2 1.5
Sprossen (horiz./vert. im Feld) ~ mullionRows grob → WindowType.muntins?: {rows, cols} je Feld Sprossengitter Sprossen-Boxen (fein) T P1 2
Nische aussparen (außen/innen) WindowType.niche?: {aussen, innen, ...} Nischenkontur Nischen-Aussparung T P2 2
Fensterbank erstellen (außen/innen) ~ sillBoard existiert, ungenutzt → WindowType.sill?: {aussen, innen, typ, masse} Bankkontur Bank-Box T P1 2
Banktyp/Winkel/ΔZ/Endverkröpfung Unterfelder von sill Detail Detail-Mesh T P3 2

2.7 Sonnenschutz · Beschlag · Geländer · Heizkörper · Eckfenster

VW-Control Status DOSSIER-Feld 2D 3D Ebene Prio Aufwand
Sonnenschutz/Rollladen erstellen + Typ WindowType.shading?: {create, kind: "raffstore"|"rollladen"|…, box, projection} Kasten-/Kastenlinien im Grundriss Kastenbox + Auskragung T P0/P1 2.5
Rollladenkasten-Maße shading.box: {w,h,d} Rechteck Box-Mesh T P0 (inkl.)
„Nische erzeugen"/„Kasten symm."/Lamellenwinkel shading-Unterfelder Detail T P2 1
Beschlag (Griff/Knauf) Geometrie/Maße SashDef.handle + WindowType.handleGeom? Griff-/Knauf-Mesh T P2 2
Geländer (Position/Höhe/Bauteile) WindowType.railing?: {...} Geländerlinien Geländer-Mesh T P3 3
Heizkörper — (eigenes Bauteil, nicht Fenster) P3
Eckfenster eigener Sonderfall (zwei Wände) T P3 5+

2.8 Attribute · Schnitte/Ansichten · Detaillierung · IFC/Energos

VW-Control Status DOSSIER-Feld 2D 3D Ebene Prio Aufwand
Attribut-Tabelle je Bestandteil (Klasse/Material/Stift/Linie/…) ~ DOSSIER hat By-Layer/By-Object-Attribute (foreground/background/hatchId/strokeWeight an Wand/Decke, NICHT an Opening) → Opening.foreground? etc. + evtl. je Bestandteil Farbe/Strich der Bestandteile Material I/T P2 3
„Klassenattribute zuweisen/entfernen" ~ AttributeSource („layer"/„object") existiert für Wand/Decke I P2 1
Automatische 2D-Darstellungen (Horizontal/Ansicht/Querschnitt) ~ generatePlan erzeugt Grundriss; Schnitt/Ansicht sind Platzhalter (DrawingLevelKind) P3
Sichtbarkeitsmatrix 3D-Objekte × Ansichten (Oben/Unten/…/Querschnitt) nicht bauen P3
Detaillierung: Option × Kategorie × Detailstufe (Augen-Tabelle) ~ DetailLevel (grob/mittel/fein) fließt in 2D + 3D Sichtbarkeit je Stufe Meshstufe I P2 2
„Für 3D die Tiefe/Mittlere/Hohe Detaillierung verwenden" Model3dOptions.detail (toWalls3d.ts:2145) Meshstufe
Beschriftung eigenes Text/Tag-System Label P3
Infos/IFC-Daten/Infopalette/Energos nicht bauen P3
„Mehrere ändern" ~ Multi-Selektion patcht bereits gemeinsame Felder I P2 1

3. Der dedizierte Dialog

3.1 Komponente & Einbettung

Neue Komponente src/ui/OpeningEditorDialog.tsx (Muster: die bestehenden *Dialog.tsx in src/ui/, z. B. SettingsDialog.tsx, TextEditorDialog.tsx, und das Overlay-Muster res-overlay + role="dialog" aus ResourceManager.tsx:371). Props (über usePanelHost/App-State geliefert):

open: boolean
openingId: string            // die editierte Instanz
kind: "window" | "door"      // steuert Sidebar-Sätze + welche Type-Liste
project: Project             // read (Typ-Listen, Bauteile, LayerCategories)
onPatchOpening(patch)        // Instanz-Felder
onPatchType(typeId, patch)   // Typ-Felder (aktueller Stil)
onSaveAsStyle(name, snapshot)// „Fenster/Tür speichern…" → neuer WindowType/DoorType
onResetToStyle()             // „zurücksetzen…"
onClose()

Öffnen aus dem ⚙. ObjectInfoPanel.OpeningSection (src/panels/ObjectInfoPanel.tsx:731, onClick → host.onEditOpeningType(...)) ruft künftig einen neuen Host-Handler host.onOpenOpeningEditor(openingId) statt setResourcesTab(...) + setResourcesOpen(true) (heutige Verdrahtung in src/App.tsx:3130). App hält openingEditorId: string | null als State und rendert <OpeningEditorDialog open={openingEditorId!=null} .../>. Der bisherige Ressourcen-Tab bleibt als Bibliotheks-/Massenpflege bestehen, ist aber nicht mehr der Primärort — das ⚙ landet direkt im reichen Dialog.

3.2 Kategorie-Sidebar (realistischer Teilmenge)

Reihenfolge und Sichtbarkeit je kind:

  1. Basis — Öffnungsart (kind), Einfügepunkt quer (insetFace/insetFromFace), Klasse (categoryCode), Detaillierung (detailLevel).
  2. Größe/Positionwidth, height, sillHeight, position (I), defaultWidth/Height/SillHeight (T).
  3. RahmenframeThickness, frameDepth, frameWidth/frameWidths, frameKind (Tür), sashOffset.
  4. Flügel/Sprossen — die SashDef[]-Tabelle (Flügel/Pfosten einfügen, Öffnungsrichtung/Anschlag je Flügel), Kämpfer-Zeilen, Sprossengitter.
  5. Oberlicht/Unterlichttransom, underlight (je Feld: Höhe, Rahmen, Gitter).
  6. Laibung/Bankreveal, sill (außen/innen), niche.
  7. Sonnenschutz/Rollladenshading (Typ, Kastenmaße, Auskragung).
  8. Attribute/Darstellung — Bestandteil-Attribute (Farbe/Strich/Material).
  9. Detaillierung — welche Bestandteile bei grob/mittel/fein sichtbar sind.

Sidebar-Sätze: bei kind==="door" entfallen Oberlicht/Unterlicht-Gitter-Details, Sonnenschutz und Bank; dafür Türblatt (leafCount, leafStyle, threshold, swing/hinge/openingDir/swingAngle).

3.3 Live-Vorschau

2D (der billige, sofort machbare Teil). DOSSIER hat den kompletten Plan→SVG-Pfad bereits: generatePlan() (src/plan/generatePlan.ts:632) → toRenderScene.ts (RScene) → sceneToPrintSvg() (src/export/sceneToPrintSvg.ts:109) bzw. planToPrintSvg() (src/export/planToPrintSvg.ts:155). Für die Vorschau ein Mini-Projekt bauen (eine kurze Wand + das editierte Opening mit den aktuellen Dialog-Werten), durch generatePlan() schicken und als SVG in ein Vorschau-<div> rendern — Grundriss oben, optional eine Elevations-Skizze. Reagiert auf jeden Patch reaktiv (React-State → neu generieren; günstig, da nur ein Element).

3D/Elevation. Zwei Optionen, in aufsteigendem Aufwand:

  • (A) günstig, empfohlen für den ersten Wurf: eine reine 2D-Ansicht/Elevation aus denselben Plan-Primitiven — VW's „Vorschau, schattiert" ist für uns nicht nötig; eine saubere Frontalansicht (Rahmen/Flügel/Sprossen/Glas als SVG) zeigt die Flügeleinteilung am aussagekräftigsten und nutzt exakt die neuen Felder.
  • (B) echte 3D-Vorschau: projectToModel3d() (src/plan/toWalls3d.ts:2149) auf das Mini-Projekt anwenden und im wgpu-Viewport (Wasm3DViewport.tsx) darstellen. Teurer (eigener Render-Kontext im Modal, WASM-Instanz) und laut Projekt-Memo NICHT per Browser/Puppeteer verifizierbar — der Nutzer testet 3D selbst in der Tauri-Dev-App. Deshalb 3D-Vorschau erst NACH der 2D-Vorschau, als eigene Phase.

Empfehlung: erste Scheibe nur 2D-Grundriss + 2D-Elevation; echte wgpu-Vorschau später.

3.4 „Als Stil speichern"-Flow

Drei Aktionen in der Stil-Leiste:

  • Fenster/Tür speichern… (onSaveAsStyle) — nimmt einen Schnappschuss der aktuellen (Typ-relevanten) Dialogwerte, fragt einen Namen ab (PromptDialog.tsx) und legt einen NEUEN WindowType/DoorType in project.windowTypes/doorTypes an (CRUD-Factories existieren: addWindowType/addDoorType in src/App.tsx ab :2273/:2304). Das editierte Opening.typeId zeigt danach auf den neuen Stil.
  • Update dieses StilspatchWindowType/patchDoorType (App :2319/:2287) auf den aktuell referenzierten typeId anwenden (wirkt auf alle Instanzen des Stils).
  • Einstellungen zurücksetzen… (onResetToStyle) — die instanzseitigen Overrides verwerfen und wieder die Typ-Defaults ziehen (Instanz-Felder auf undefined setzen, sodass die Resolver getWindowType/getDoorType (src/model/types.ts:2060/:2064) greifen).

TYP-vs-INSTANZ-Regel im Dialog sichtbar machen: Typ-Felder tragen eine dezente „Stil"-Markierung; ein instanzseitig übersteuertes Feld zeigt einen „abweichend vom Stil"-Indikator + „zurücksetzen". Das ist genau VW's Stil-/Override-Logik.


4. Modell-Erweiterungsplan (phasiert)

Alle Felder additiv/optional (Alt-Projekte laden unverändert — die bestehende Konvention der Datei, vgl. wingCount?, sliceTermination Default). Neue Felder auf WindowType/DoorType in src/model/types.ts.

P0 — die erste reiche Scheibe (Kern-Tiefe)

// Ein Flügel- oder Pfosten-Eintrag der Flügeleinteilung.
type OpeningKind = "dreh" | "kipp" | "drehkipp" | "fest" | "schiebe";
interface SashDef {
  kind: "fluegel" | "pfosten";
  autoWidth?: boolean;      // gleichmäßig aufteilen (Default true)
  width?: number;           // feste Flügelbreite (m), wenn !autoWidth
  postWidth?: number;       // Pfostenbreite (m), nur kind="pfosten"
  opening?: OpeningKind;    // Öffnungsart DIESES Flügels
  hingeSide?: "left" | "right"; // Anschlag
}
interface WindowType {
  // … bestehend …
  sashes?: SashDef[];       // ersetzt/erweitert wingCount; fehlt ⇒ aus wingCount abgeleitet
  shading?: {               // Rollladen/Sonnenschutz-Kasten (P0-Minimalfassung)
    create: boolean;
    kind: "rollladen" | "raffstore" | "markise";
    box: { width: number; height: number; depth: number };
  };
  glazingPanes?: 1 | 2 | 3; // Verglasung endlich RENDER-wirksam (heute: glazing unbenutzt)
}

Rationale/Render-Freischaltung:

  • sashes — schaltet echte, ungleichmäßige Flügel/Pfosten + Öffnungsrichtung je Flügel frei; treibt neue Pfostenlinien in windowSymbol/mullionLines (src/geometry/opening.ts) und Pfosten-Boxen in frameMeshesForOpening (src/plan/toWalls3d.ts:1938). Höchster sichtbarer Mehrwert.
  • shading — der Rollladenkasten ist ein einzelnes Kästchen: eine 2D-Rechteckkontur über der Öffnung (neuer Zeichenzweig in addOpeningSymbol) + eine Box in emitOpeningFrames/eigener Emitter. Wenig Code, große „Reichhaltigkeit".
  • glazingPanesglassPanesForOpening (:1965) liest die Scheibenzahl statt konstant EINE Scheibe zu zeichnen; 2D-Glaslinien-Anzahl analog. Schließt die auffälligste Konsum-Lücke.

P1 — Tiefe der Flügel/Felder

interface SashDef { /* + */ rebate?: number; openAngle?: number; handle?: "drueck"|"knauf"|"none"; }
interface WindowType {
  frameWidths?: { left: number; right: number; top: number; bottom: number };
  frameSymmetric?: boolean;
  uniformPosts?: boolean;
  transom?: { height: number; frame?: number; muntins?: { rows: number; cols: number } };
  sill?: { aussen?: SillDef; innen?: SillDef };
  muntins?: { rows: number; cols: number }; // Sprossen im Hauptfeld
}

Rationale: asymmetrische Rahmenbreiten, richtiges Oberlicht (statt nur feste Scheibe via transomHeight), Fensterbank (heute sillBoard ungenutzt), Sprossengitter. Alles hängt an vorhandenen Zeichen-/Mesh-Funktionen; nur Parameterausbau.

P2 — Detailbestandteile

interface WindowType {
  reveal?: { create: boolean; depth: number; thickness: number };   // Laibungsverkleidung
  underlight?: { height: number; frame?: number };                  // Unterlicht
  niche?: { aussen?: boolean; innen?: boolean; depth: number };     // Nische
  headShape?: "eckig" | "schraeg_links" | "schraeg_rechts" | "spitz" | "rund";
  handleGeom?: { kind: "quader" | "rund"; masse: Record<string, number> };
}
interface Opening { foreground?: string; background?: string; strokeWeight?: number; } // Bestandteil-Attribute

Rationale: Form (Rundbogen/Schräge → neue Öffnungsumriss-Geometrie), Laibung/Nische, Beschlag-Geometrie, per-Instanz-Attribute (analog Wand/Decke, die es schon haben).

P3 — VW-Ballast (nur wenn je nachgefragt)

Bemaßungs-Bezugsschemata (B1..B5/H1..H7), Sichtbarkeitsmatrix 3D×Ansichten, IFC/Energos, Geländer, Heizkörper, Eckfenster, eigenes Symbol, Fassadenmodul-Versatz. Siehe Abschnitt 5.

Höchster-Wert-Reihenfolge (verdichtet): (1) sashes inkl. Öffnungsrichtung je Flügel, (2) mehrscheibige Verglasung, (3) Rollladenkasten, (4) Bank/Nische, (5) Sprossengitter, (6) Form (Rund/Spitz/Schräg), (7) Ober-/Unterlicht als eigene Felder.


5. Was NICHT bauen / Risiken

Nicht bauen (VW-Komplexität ohne DOSSIER-Nutzen):

  • Sichtbarkeitsmatrix „3D-Objekte × 9 Ansichten" (Oben/Unten/Horizontalschnitt/ Vorne/Hinten/Frontalschnitt/Links/Rechts/Querschnitt). DOSSIER rendert heute primär Grundriss; Schnitt/Ansicht sind DrawingLevelKind-Platzhalter. Eine Matrix mit ~13×9 An/Aus-Zellen ist Pflegehölle ohne Zielrenderer. DetailLevel (grob/mittel/fein) genügt als Sichtbarkeitsachse.
  • Bemaßungs-Bezugssystem (Roh-/Fertigmaß, B1..B5/H1..H7). DOSSIER trägt lichte Maße; eine parametrische Öffnungs-Maßkette existiert nicht. Sehr teuer, geringer Nutzen für ein CAAD-Werkzeug dieser Reife.
  • IFC-Datenmapping-Tiefe, Energos, Infopalette. Kein IFC-Export-Pfad vorhanden; bewusst weglassen (kein Schein-Feature, vgl. die Modell-Doku-Konvention).
  • Heizkörper, Geländer, Eckfenster als Fenster-Unterobjekte — das sind eigene Bauteile bzw. topologische Sonderfälle (zwei Wände). Nicht ins Fenster packen.
  • Eigenes Symbol verwenden (Drawing2D-Override je Stil) — erst wenn ein Symbol-/Blocksystem existiert.

Risiken:

  • 3D-Vorschau nicht als „korrekt" verkaufen. Laut Projekt-Memo werden render3d/Wasm3DViewport-Änderungen NICHT per Puppeteer/Browser verifiziert; der Nutzer testet 3D selbst in der Tauri-Dev-App. Der Dialog darf 3D anzeigen, aber diese Studie/Implementierung behauptet keine visuelle Korrektheit der 3D-Vorschau — nur der 2D-Pfad (generatePlan→SVG) ist headless prüfbar.
  • Typ-vs-Instanz-Verwirrung. Ohne klaren „vom Stil abweichend"-Indikator wird unklar, ob eine Änderung den Stil (alle Instanzen) oder nur dieses Fenster trifft. Muss im UI explizit sein (§3.4).
  • sashes vs. wingCount Migration. wingCount bleibt als Fallback; sashes hat Vorrang, wenn gesetzt. Resolver in resolveOpeningFrame (toWalls3d.ts:1845) und windowSymbol müssen beide Wege beherrschen, sonst brechen Alt-Projekte oder die ObjectInfoPanel-Flügelanzahl-Eingabe.
  • Modal-Render-Kosten. Live-Vorschau bei jedem Tastendruck neu generieren ist ok für ein Mini-Projekt, aber Patches sollten (leicht) entprellt werden.

6. Empfohlene erste Scheibe (P0, konkret)

Kleinstes End-to-End-Inkrement, das sich schon „reich" anfühlt:

Umfang: Dialog-Shell + Live-2D-Vorschau + Flügeleinteilung (n Flügel mit Öffnungsrichtung je Flügel) + mehrscheibige Verglasung + Rollladenkasten + „Als Stil speichern".

Build-Reihenfolge:

  1. Modell (P0-Felder). SashDef, WindowType.sashes?, WindowType.shading?, WindowType.glazingPanes? in src/model/types.ts ergänzen (additiv). Resolver getWindowType bleibt; wingCountsashes-Ableitung als Helper.
  2. Renderer-Konsum (2D zuerst, headless prüfbar).
    • windowSymbol/mullionLines (src/geometry/opening.ts) auf sashes umstellen (ungleichmäßige Pfostenlagen + Öffnungsrichtung je Flügel als Öffnungslinien).
    • glassPanesForOpening/2D-Glaslinien auf glazingPanes (statt konstant 1/detail).
    • neuer Zeichenzweig in addOpeningSymbol (src/plan/generatePlan.ts:2214) für die Rollladenkasten-Kontur; 3D-Box später.
    • Unit-Tests gegen generatePlan()-Output (Primitive zählen) — das ist der verifizierbare Beweis, nicht nur „grüne Typecheck".
  3. Dialog-Shell. src/ui/OpeningEditorDialog.tsx (Overlay res-overlay + role="dialog"), Sidebar mit den Kategorien Basis / Größe/Position / Rahmen / Flügel-Sprossen / Sonnenschutz. Nur diese fünf für P0.
  4. Live-2D-Vorschau. Mini-Projekt (kurze Wand + editiertes Opening) → generatePlan()sceneToPrintSvg()/planToPrintSvg()<div>. Grundriss + Elevations-SVG. Reaktiv auf Patches (leicht entprellt).
  5. Flügel-Tabelle. UI-Tabelle mit „Flügel einfügen / Pfosten einfügen / Löschen" und je Zeile Öffnungsart + Anschlag (SashDef). Schreibt WindowType.sashes.
  6. „Als Stil speichern". Stil-Leiste + onSaveAsStyleaddWindowType (src/App.tsx:2304) mit PromptDialog-Namensabfrage; „zurücksetzen" verwirft Instanz-Overrides.
  7. ⚙-Verdrahtung. ObjectInfoPanel.OpeningSection (:731) → neuer Host-Handler onOpenOpeningEditor(openingId); App-State openingEditorId. Ressourcen-Tab bleibt als Bibliothek erhalten.

Definition of Done (P0): Aus dem ⚙ öffnet sich der Dialog; man setzt 3 Flügel, davon einer Dreh-links / einer Dreh-rechts / einer fest, wählt Dreifachverglasung und einen Rollladenkasten; die 2D-Vorschau zeigt die Pfosten/Öffnungslinien/Kasten sofort; „Speichern…" legt einen benannten Fensterstil an, der im Typ-Dropdown der OpeningSection erscheint. Verifikation über generatePlan()-Primitiv-Tests (2D) — 3D-Box erst danach, vom Nutzer in der Tauri-App geprüft.