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.
This commit is contained in:
2026-07-10 01:13:15 +02:00
parent 1bbe814ab1
commit c734802555
9 changed files with 1696 additions and 4 deletions
+70
View File
@@ -69,6 +69,7 @@ import { exportIfcSpf } from "./export/exportIfc";
import { exportObj, exportStl } from "./export/exportMesh";
import type { ScheduleKind } from "./export/exportSchedule";
import { SettingsDialog } from "./ui/SettingsDialog";
import { OpeningEditorDialog } from "./ui/OpeningEditorDialog";
import { ExportSaveDialog } from "./ui/ExportSaveDialog";
import type { ExportSaveFormat } from "./ui/ExportSaveDialog";
import { PromptDialog } from "./ui/PromptDialog";
@@ -414,6 +415,9 @@ export default function App() {
// Angeforderter Start-Tab des Ressourcen-Fensters (z. B. „doorStyles", wenn man
// von einer gewählten Tür in den Typeditor springt).
const [resourcesTab, setResourcesTab] = useState<ResourceTabId>("components");
// Offener Fenster-/Tür-Einstellungsdialog (⚙ der OpeningSection) — ID der
// editierten Öffnung, oder null wenn geschlossen.
const [openingEditorId, setOpeningEditorId] = useState<string | null>(null);
// Aktives Layout-Blatt im HAUPT-Viewport (DOSSIER A3, Phase 2b): Id des offenen
// Layouts oder null (= normale Modell-Ansicht). Transienter UI-State; das
@@ -2340,6 +2344,57 @@ export default function App() {
windowTypes: (p.windowTypes ?? []).filter((wt) => wt.id !== id),
};
});
// „Als Stil speichern": aktuellen Fenster-/Tür-Typ der Öffnung als NEUEN
// benannten Stil ablegen und der Öffnung zuweisen (VW „Fenster speichern…").
// Ohne Bezugstyp wird aus den Default-Feldern ein frischer Stil erzeugt.
const saveOpeningStyle = (openingId: string, name: string) =>
setProject((p) => {
const o = (p.openings ?? []).find((x) => x.id === openingId);
if (!o) return p;
if (o.kind === "door") {
const base = (p.doorTypes ?? []).find((d) => d.id === o.typeId);
const nt: DoorType = base
? { ...base, id: `dt-${Date.now()}`, name }
: {
id: `dt-${Date.now()}`,
name,
kind: "dreh",
leafCount: 1,
leafStyle: "glatt",
frameThickness: 0.05,
defaultWidth: 0.9,
defaultHeight: 2.0,
};
return {
...p,
doorTypes: [...(p.doorTypes ?? []), nt],
openings: (p.openings ?? []).map((x) =>
x.id === openingId ? { ...x, typeId: nt.id } : x,
),
};
}
const base = (p.windowTypes ?? []).find((w) => w.id === o.typeId);
const nt: WindowType = base
? { ...base, id: `ft-${Date.now()}`, name }
: {
id: `ft-${Date.now()}`,
name,
kind: "drehkipp",
wingCount: 1,
glazing: "zweifach",
frameThickness: 0.06,
defaultSillHeight: 0.9,
defaultWidth: 1.2,
defaultHeight: 1.4,
};
return {
...p,
windowTypes: [...(p.windowTypes ?? []), nt],
openings: (p.openings ?? []).map((x) =>
x.id === openingId ? { ...x, typeId: nt.id } : x,
),
};
});
const addStairType = () =>
setProject((p) => {
const st: StairType = {
@@ -3166,6 +3221,7 @@ export default function App() {
setResourcesTab(kind === "door" ? "doorStyles" : "windowStyles");
setResourcesOpen(true);
},
onOpenOpeningEditor: (id) => setOpeningEditorId(id),
// ── Dach-Attribute (nur bei selektiertem Dach) ─────────────────────────
onSetRoofPatch: (patch) => {
if (selection?.kind === "roof") updateRoof(selection.id, patch);
@@ -4959,6 +5015,20 @@ export default function App() {
/>
)}
{/* Reicher Fenster-/Tür-Einstellungsdialog (⚙ der OpeningSection). */}
{openingEditorId != null && (
<OpeningEditorDialog
openingId={openingEditorId}
project={project}
onPatchWindowType={patchWindowType}
onPatchDoorType={patchDoorType}
onPatchOpening={(patch) => updateOpening(openingEditorId, patch)}
onSetOpeningType={(typeId) => updateOpening(openingEditorId, { typeId: typeId || undefined })}
onSaveAsStyle={(name) => saveOpeningStyle(openingEditorId, name)}
onClose={() => setOpeningEditorId(null)}
/>
)}
{/* Modaler Import-Dialog (DXF/DWG) offen, sobald eine Datei gewählt/
fallengelassen wurde. Liefert eine strukturierte Entscheidung an
runImport (Ebene anlegen / Drawings anhängen / Meshes als Kontext). */}