# 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.threshold` → `hasSill` 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 ``. 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/Position** — `width`, `height`, `sillHeight`, `position` (I), `defaultWidth/Height/SillHeight` (T). 3. **Rahmen** — `frameThickness`, `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/Unterlicht** — `transom`, `underlight` (je Feld: Höhe, Rahmen, Gitter). 6. **Laibung/Bank** — `reveal`, `sill` (außen/innen), `niche`. 7. **Sonnenschutz/Rollladen** — `shading` (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-`
` 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 Stils** — `patchWindowType`/`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) ```ts // 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". - `glazingPanes` — `glassPanesForOpening` (: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 ```ts 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 ```ts 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 }; } 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; `wingCount`→`sashes`-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()` → `
`. 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 + `onSaveAsStyle` → `addWindowType` (`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.