# 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 `<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/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-`<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 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<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; `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()` → `<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 + `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.
