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.
27 KiB
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):
- Kategorie-Sidebar links (Basis, Größe/Position, Rahmen, Flügel/Sprossen, Oberlicht/Unterlicht, Laibung/Bank, Sonnenschutz/Rollladen, Attribute/Darstellung, Detaillierung).
- Parameter-Panel in der Mitte (die Controls der gewählten Kategorie).
- Live-Vorschau rechts: eine 2D-Plan-Vorschau (aus
generatePlan()) und eine 3D-Ansicht/Elevation (ausprojectToModel3d()), 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 (glassPanesForOpeninginsrc/plan/toWalls3d.ts:1965ignoriertglazing); 2D leitet die Glaslinien- Anzahl allein ausDetailLevelab (addOpeningSymbolinsrc/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ßerleafStyle==="glas"→ 3D-Verglasung an) sind ungenutzt.WindowType.sillBoardungenutzt;DoorType.threshold→hasSillgenutzt.- 3D-Mittelpfosten/Kämpfer und Sprossen werden nur bei
detail==="fein"emittiert (frameMeshesForOpeninginsrc/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:
- Basis — Öffnungsart (
kind), Einfügepunkt quer (insetFace/insetFromFace), Klasse (categoryCode), Detaillierung (detailLevel). - Größe/Position —
width,height,sillHeight,position(I),defaultWidth/Height/SillHeight(T). - Rahmen —
frameThickness,frameDepth,frameWidth/frameWidths,frameKind(Tür),sashOffset. - Flügel/Sprossen — die
SashDef[]-Tabelle (Flügel/Pfosten einfügen, Öffnungsrichtung/Anschlag je Flügel), Kämpfer-Zeilen, Sprossengitter. - Oberlicht/Unterlicht —
transom,underlight(je Feld: Höhe, Rahmen, Gitter). - Laibung/Bank —
reveal,sill(außen/innen),niche. - Sonnenschutz/Rollladen —
shading(Typ, Kastenmaße, Auskragung). - Attribute/Darstellung — Bestandteil-Attribute (Farbe/Strich/Material).
- 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 NEUENWindowType/DoorTypeinproject.windowTypes/doorTypesan (CRUD-Factories existieren:addWindowType/addDoorTypeinsrc/App.tsxab :2273/:2304). Das editierteOpening.typeIdzeigt danach auf den neuen Stil. - Update dieses Stils —
patchWindowType/patchDoorType(App :2319/:2287) auf den aktuell referenziertentypeIdanwenden (wirkt auf alle Instanzen des Stils). - Einstellungen zurücksetzen… (
onResetToStyle) — die instanzseitigen Overrides verwerfen und wieder die Typ-Defaults ziehen (Instanz-Felder aufundefinedsetzen, sodass die ResolvergetWindowType/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 inwindowSymbol/mullionLines(src/geometry/opening.ts) und Pfosten-Boxen inframeMeshesForOpening(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 inaddOpeningSymbol) + eine Box inemitOpeningFrames/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
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).
sashesvs.wingCountMigration.wingCountbleibt als Fallback;sasheshat Vorrang, wenn gesetzt. Resolver inresolveOpeningFrame(toWalls3d.ts:1845) undwindowSymbolmüssen beide Wege beherrschen, sonst brechen Alt-Projekte oder dieObjectInfoPanel-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:
- Modell (P0-Felder).
SashDef,WindowType.sashes?,WindowType.shading?,WindowType.glazingPanes?insrc/model/types.tsergänzen (additiv). ResolvergetWindowTypebleibt;wingCount→sashes-Ableitung als Helper. - Renderer-Konsum (2D zuerst, headless prüfbar).
windowSymbol/mullionLines(src/geometry/opening.ts) aufsashesumstellen (ungleichmäßige Pfostenlagen + Öffnungsrichtung je Flügel als Öffnungslinien).glassPanesForOpening/2D-Glaslinien aufglazingPanes(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".
- Dialog-Shell.
src/ui/OpeningEditorDialog.tsx(Overlayres-overlay+role="dialog"), Sidebar mit den Kategorien Basis / Größe/Position / Rahmen / Flügel-Sprossen / Sonnenschutz. Nur diese fünf für P0. - Live-2D-Vorschau. Mini-Projekt (kurze Wand + editiertes
Opening) →generatePlan()→sceneToPrintSvg()/planToPrintSvg()→<div>. Grundriss + Elevations-SVG. Reaktiv auf Patches (leicht entprellt). - Flügel-Tabelle. UI-Tabelle mit „Flügel einfügen / Pfosten einfügen / Löschen"
und je Zeile Öffnungsart + Anschlag (
SashDef). SchreibtWindowType.sashes. - „Als Stil speichern". Stil-Leiste +
onSaveAsStyle→addWindowType(src/App.tsx:2304) mitPromptDialog-Namensabfrage; „zurücksetzen" verwirft Instanz-Overrides. - ⚙-Verdrahtung.
ObjectInfoPanel.OpeningSection(:731) → neuer Host-HandleronOpenOpeningEditor(openingId); App-StateopeningEditorId. 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.