Tür/Fenster-Typmodell vertiefen: Rahmenart, Rahmenbreite, Schichteinzug, Oberlicht, Kämpfer
Erweitert DoorType/WindowType um architektonische Detailparameter und verdrahtet sie im ResourceManager-Typeditor (Tür-/Fenstertypen): - Tür: frameKind (Zarge/Blockrahmen), frameWidth (Ansichtsbreite Profil), insetFromFace + insetFace (Schichteinzug ab Aussen-/Innenfläche), transomHeight (Oberlicht über Kämpfer). - Fenster: mullionRows (horizontale Kämpfer-Teilung), frameWidth, insetFromFace + insetFace, transomHeight. - Seeds: Haustür „Blockrahmen (Oberlicht)" + Fenster „2-flügl. + Oberlicht" demonstrieren Einzug/Oberlicht/Kämpfer. - i18n de/en für alle neuen Felder + Optionen. Reine Modell-/UI-Schicht; 2D- und 3D-Darstellung folgen separat. Alle Felder optional → rückwärtskompatibel. tsc sauber.
This commit is contained in:
@@ -632,6 +632,16 @@ export const de = {
|
|||||||
"resources.field.defaultWidth": "Lichtbreite (m)",
|
"resources.field.defaultWidth": "Lichtbreite (m)",
|
||||||
"resources.field.defaultHeight": "Lichthöhe (m)",
|
"resources.field.defaultHeight": "Lichthöhe (m)",
|
||||||
"resources.field.threshold": "Bodenschwelle",
|
"resources.field.threshold": "Bodenschwelle",
|
||||||
|
"resources.field.frameKind": "Rahmenart",
|
||||||
|
"resources.field.frameWidth": "Rahmenbreite (m)",
|
||||||
|
"resources.field.insetFromFace": "Schichteinzug (m)",
|
||||||
|
"resources.field.insetFace": "Einzug ab Fläche",
|
||||||
|
"resources.field.transomHeight": "Oberlicht-Höhe (m)",
|
||||||
|
"resources.field.mullionRows": "Kämpfer-Zeilen",
|
||||||
|
"resources.frameKind.zarge": "Zarge",
|
||||||
|
"resources.frameKind.blockrahmen": "Blockrahmen",
|
||||||
|
"resources.insetFace.aussen": "Aussen",
|
||||||
|
"resources.insetFace.innen": "Innen",
|
||||||
"resources.field.windowKind": "Öffnungsart",
|
"resources.field.windowKind": "Öffnungsart",
|
||||||
"resources.field.wingCount": "Flügelzahl",
|
"resources.field.wingCount": "Flügelzahl",
|
||||||
"resources.field.glazing": "Verglasung",
|
"resources.field.glazing": "Verglasung",
|
||||||
|
|||||||
@@ -627,6 +627,16 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"resources.field.defaultWidth": "Clear width (m)",
|
"resources.field.defaultWidth": "Clear width (m)",
|
||||||
"resources.field.defaultHeight": "Clear height (m)",
|
"resources.field.defaultHeight": "Clear height (m)",
|
||||||
"resources.field.threshold": "Threshold",
|
"resources.field.threshold": "Threshold",
|
||||||
|
"resources.field.frameKind": "Frame type",
|
||||||
|
"resources.field.frameWidth": "Frame width (m)",
|
||||||
|
"resources.field.insetFromFace": "Layer inset (m)",
|
||||||
|
"resources.field.insetFace": "Inset from face",
|
||||||
|
"resources.field.transomHeight": "Transom height (m)",
|
||||||
|
"resources.field.mullionRows": "Transom rows",
|
||||||
|
"resources.frameKind.zarge": "Lining",
|
||||||
|
"resources.frameKind.blockrahmen": "Block frame",
|
||||||
|
"resources.insetFace.aussen": "Outer",
|
||||||
|
"resources.insetFace.innen": "Inner",
|
||||||
"resources.field.windowKind": "Opening style",
|
"resources.field.windowKind": "Opening style",
|
||||||
"resources.field.wingCount": "Wing count",
|
"resources.field.wingCount": "Wing count",
|
||||||
"resources.field.glazing": "Glazing",
|
"resources.field.glazing": "Glazing",
|
||||||
|
|||||||
@@ -212,9 +212,29 @@ export const sampleProject: Project = {
|
|||||||
leafCount: 1,
|
leafCount: 1,
|
||||||
leafStyle: "glatt",
|
leafStyle: "glatt",
|
||||||
frameThickness: 0.05,
|
frameThickness: 0.05,
|
||||||
|
frameKind: "zarge",
|
||||||
|
frameWidth: 0.06,
|
||||||
defaultWidth: 0.9,
|
defaultWidth: 0.9,
|
||||||
defaultHeight: 2.0,
|
defaultHeight: 2.0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Aussentür: Blockrahmen, in die Aussenschale zurückgesetzt, mit Oberlicht.
|
||||||
|
id: "door-entry",
|
||||||
|
name: "Haustür Blockrahmen (Oberlicht)",
|
||||||
|
kind: "dreh",
|
||||||
|
leafCount: 1,
|
||||||
|
leafStyle: "glas",
|
||||||
|
glazingRatio: 0.35,
|
||||||
|
frameThickness: 0.07,
|
||||||
|
frameKind: "blockrahmen",
|
||||||
|
frameWidth: 0.09,
|
||||||
|
insetFromFace: 0.14,
|
||||||
|
insetFace: "aussen",
|
||||||
|
transomHeight: 0.45,
|
||||||
|
threshold: true,
|
||||||
|
defaultWidth: 1.1,
|
||||||
|
defaultHeight: 2.1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
windowTypes: [
|
windowTypes: [
|
||||||
{
|
{
|
||||||
@@ -224,10 +244,28 @@ export const sampleProject: Project = {
|
|||||||
wingCount: 1,
|
wingCount: 1,
|
||||||
glazing: "zweifach",
|
glazing: "zweifach",
|
||||||
frameThickness: 0.06,
|
frameThickness: 0.06,
|
||||||
|
frameWidth: 0.06,
|
||||||
defaultSillHeight: 0.9,
|
defaultSillHeight: 0.9,
|
||||||
defaultWidth: 1.2,
|
defaultWidth: 1.2,
|
||||||
defaultHeight: 1.4,
|
defaultHeight: 1.4,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Zweiflügliges Fenster mit Kämpfer, Oberlicht und Schichteinzug.
|
||||||
|
id: "window-double",
|
||||||
|
name: "Fenster 2-flügl. + Oberlicht",
|
||||||
|
kind: "drehkipp",
|
||||||
|
wingCount: 2,
|
||||||
|
mullionRows: 1,
|
||||||
|
glazing: "dreifach",
|
||||||
|
frameThickness: 0.07,
|
||||||
|
frameWidth: 0.07,
|
||||||
|
insetFromFace: 0.12,
|
||||||
|
insetFace: "aussen",
|
||||||
|
transomHeight: 0.4,
|
||||||
|
defaultSillHeight: 0.85,
|
||||||
|
defaultWidth: 1.8,
|
||||||
|
defaultHeight: 1.9,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
stairTypes: [
|
stairTypes: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -311,6 +311,36 @@ export interface DoorType {
|
|||||||
frameThickness: number;
|
frameThickness: number;
|
||||||
/** Rahmen-/Zargen-Tiefe in Wandrichtung (Meter); fehlt ⇒ aus Wanddicke. */
|
/** Rahmen-/Zargen-Tiefe in Wandrichtung (Meter); fehlt ⇒ aus Wanddicke. */
|
||||||
frameDepth?: number;
|
frameDepth?: number;
|
||||||
|
/**
|
||||||
|
* Rahmenart:
|
||||||
|
* • "zarge" — Zarge, umschliesst die Laibung (schmales Profil, in der
|
||||||
|
* Wandlaibung sitzend — der Standard im Innenausbau).
|
||||||
|
* • "blockrahmen" — Blockrahmen, sitzt als kräftiges Rechteckprofil VOR/auf
|
||||||
|
* der Laibung (typisch bei Aussentüren/älterem Bestand).
|
||||||
|
* Fehlt es, gilt "zarge".
|
||||||
|
*/
|
||||||
|
frameKind?: "zarge" | "blockrahmen";
|
||||||
|
/**
|
||||||
|
* Ansichtsbreite des Rahmenprofils (Meter) — die sichtbare Rahmen-Randbreite
|
||||||
|
* in der Öffnungsebene (Elevation), NICHT die Tiefe quer zur Wand. Fehlt es,
|
||||||
|
* gilt ein schmaler Default (~0.06 m). Steuert 2D-Rahmenkontur + 3D-Rahmen.
|
||||||
|
*/
|
||||||
|
frameWidth?: number;
|
||||||
|
/**
|
||||||
|
* Schichteinzug: Abstand der Rahmen-Vorderkante von einer Wandfläche (Meter).
|
||||||
|
* 0/fehlt ⇒ bündig. > 0 ⇒ der Rahmen sitzt um diesen Betrag in die Wand
|
||||||
|
* zurückgesetzt (z. B. hinter die Aussenschale eines mehrschichtigen Aufbaus).
|
||||||
|
* Gemessen von der über {@link insetFace} gewählten Fläche.
|
||||||
|
*/
|
||||||
|
insetFromFace?: number;
|
||||||
|
/** Von welcher Wandfläche der {@link insetFromFace} gemessen wird; Default "aussen". */
|
||||||
|
insetFace?: "aussen" | "innen";
|
||||||
|
/**
|
||||||
|
* Oberlicht: Höhe eines festen, verglasten Oberlichts über dem Türblatt
|
||||||
|
* (Meter). 0/fehlt ⇒ kein Oberlicht. Das Oberlicht sitzt oberhalb eines
|
||||||
|
* Kämpfers innerhalb derselben lichten Öffnungshöhe.
|
||||||
|
*/
|
||||||
|
transomHeight?: number;
|
||||||
/** Default-Lichtbreite (Meter) für neu platzierte Türen dieses Typs. */
|
/** Default-Lichtbreite (Meter) für neu platzierte Türen dieses Typs. */
|
||||||
defaultWidth: number;
|
defaultWidth: number;
|
||||||
/** Default-Lichthöhe (Meter). */
|
/** Default-Lichthöhe (Meter). */
|
||||||
@@ -331,12 +361,37 @@ export interface WindowType {
|
|||||||
kind: "dreh" | "kipp" | "drehkipp" | "fest" | "schiebe";
|
kind: "dreh" | "kipp" | "drehkipp" | "fest" | "schiebe";
|
||||||
/** Anzahl Flügel (1–4) → Mittelpfosten = wingCount − 1 (vgl. `Opening.wingCount`). */
|
/** Anzahl Flügel (1–4) → Mittelpfosten = wingCount − 1 (vgl. `Opening.wingCount`). */
|
||||||
wingCount: number;
|
wingCount: number;
|
||||||
|
/**
|
||||||
|
* Anzahl horizontaler Felder (Kämpfer-Zeilen): 1 = keine Querteilung, 2 = ein
|
||||||
|
* Kämpfer, usw. Zusammen mit {@link wingCount} (Spalten) ergibt sich das
|
||||||
|
* Sprossen-/Flügelraster. Fehlt es, gilt 1.
|
||||||
|
*/
|
||||||
|
mullionRows?: number;
|
||||||
/** Verglasung: Einfach/Zweifach/Dreifach (3D-Scheibenzahl). */
|
/** Verglasung: Einfach/Zweifach/Dreifach (3D-Scheibenzahl). */
|
||||||
glazing: "einfach" | "zweifach" | "dreifach";
|
glazing: "einfach" | "zweifach" | "dreifach";
|
||||||
/** Rahmenstärke quer zur Wand (Meter). */
|
/** Rahmenstärke quer zur Wand (Meter). */
|
||||||
frameThickness: number;
|
frameThickness: number;
|
||||||
/** Rahmen-Tiefe in Wandrichtung (Meter); fehlt ⇒ aus Wanddicke. */
|
/** Rahmen-Tiefe in Wandrichtung (Meter); fehlt ⇒ aus Wanddicke. */
|
||||||
frameDepth?: number;
|
frameDepth?: number;
|
||||||
|
/**
|
||||||
|
* Ansichtsbreite des Rahmen-/Flügelprofils (Meter) — sichtbare Randbreite in
|
||||||
|
* der Öffnungsebene. Fehlt es, gilt ein schmaler Default (~0.06 m). Steuert die
|
||||||
|
* 2D-Rahmen-/Sprossenkontur und den 3D-Rahmen.
|
||||||
|
*/
|
||||||
|
frameWidth?: number;
|
||||||
|
/**
|
||||||
|
* Schichteinzug: Abstand der Rahmen-Vorderkante von einer Wandfläche (Meter).
|
||||||
|
* 0/fehlt ⇒ bündig; > 0 ⇒ der Rahmen sitzt zurückgesetzt (klassisch hinter der
|
||||||
|
* Aussenschale eines mehrschichtigen Wandaufbaus). Von {@link insetFace} gemessen.
|
||||||
|
*/
|
||||||
|
insetFromFace?: number;
|
||||||
|
/** Von welcher Wandfläche der {@link insetFromFace} gemessen wird; Default "aussen". */
|
||||||
|
insetFace?: "aussen" | "innen";
|
||||||
|
/**
|
||||||
|
* Oberlicht: Höhe eines festen, verglasten Oberlichts über dem Hauptflügel
|
||||||
|
* (Meter), abgeteilt durch einen Kämpfer. 0/fehlt ⇒ kein Oberlicht.
|
||||||
|
*/
|
||||||
|
transomHeight?: number;
|
||||||
/** Default-Brüstungshöhe über Wand-UK (Meter). */
|
/** Default-Brüstungshöhe über Wand-UK (Meter). */
|
||||||
defaultSillHeight: number;
|
defaultSillHeight: number;
|
||||||
/** Default-Lichtbreite (Meter). */
|
/** Default-Lichtbreite (Meter). */
|
||||||
|
|||||||
@@ -1977,6 +1977,18 @@ const DOOR_LEAF_STYLE_OPTIONS: { value: DoorType["leafStyle"]; labelKey: string
|
|||||||
{ value: "glas", labelKey: "resources.leafStyle.glas" },
|
{ value: "glas", labelKey: "resources.leafStyle.glas" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const DOOR_FRAME_KIND_OPTIONS: { value: NonNullable<DoorType["frameKind"]>; labelKey: string }[] = [
|
||||||
|
{ value: "zarge", labelKey: "resources.frameKind.zarge" },
|
||||||
|
{ value: "blockrahmen", labelKey: "resources.frameKind.blockrahmen" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Bezugsfläche des Schichteinzugs — von aussen oder innen gemessen. Für Tür UND
|
||||||
|
// Fenster genutzt (gleiche Semantik).
|
||||||
|
const INSET_FACE_OPTIONS: { value: "aussen" | "innen"; labelKey: string }[] = [
|
||||||
|
{ value: "aussen", labelKey: "resources.insetFace.aussen" },
|
||||||
|
{ value: "innen", labelKey: "resources.insetFace.innen" },
|
||||||
|
];
|
||||||
|
|
||||||
const WINDOW_KIND_OPTIONS: { value: WindowType["kind"]; labelKey: string }[] = [
|
const WINDOW_KIND_OPTIONS: { value: WindowType["kind"]; labelKey: string }[] = [
|
||||||
{ value: "dreh", labelKey: "resources.windowKind.dreh" },
|
{ value: "dreh", labelKey: "resources.windowKind.dreh" },
|
||||||
{ value: "kipp", labelKey: "resources.windowKind.kipp" },
|
{ value: "kipp", labelKey: "resources.windowKind.kipp" },
|
||||||
@@ -2172,6 +2184,46 @@ function DoorStylesTab({
|
|||||||
min={0}
|
min={0}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
<FieldRow label={t("resources.field.frameKind")}>
|
||||||
|
<SelectField
|
||||||
|
value={door.frameKind ?? "zarge"}
|
||||||
|
onChange={(frameKind) => patch({ frameKind })}
|
||||||
|
options={DOOR_FRAME_KIND_OPTIONS.map((o) => ({ value: o.value, label: t(o.labelKey) }))}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
<FieldRow label={t("resources.field.frameWidth")}>
|
||||||
|
<NumberField
|
||||||
|
value={door.frameWidth ?? 0.06}
|
||||||
|
onChange={(frameWidth) => patch({ frameWidth })}
|
||||||
|
step={0.005}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
<FieldRow label={t("resources.field.insetFromFace")}>
|
||||||
|
<NumberField
|
||||||
|
value={door.insetFromFace ?? 0}
|
||||||
|
onChange={(insetFromFace) => patch({ insetFromFace })}
|
||||||
|
step={0.01}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
{(door.insetFromFace ?? 0) > 0 && (
|
||||||
|
<FieldRow label={t("resources.field.insetFace")}>
|
||||||
|
<SelectField
|
||||||
|
value={door.insetFace ?? "aussen"}
|
||||||
|
onChange={(insetFace) => patch({ insetFace })}
|
||||||
|
options={INSET_FACE_OPTIONS.map((o) => ({ value: o.value, label: t(o.labelKey) }))}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
)}
|
||||||
|
<FieldRow label={t("resources.field.transomHeight")}>
|
||||||
|
<NumberField
|
||||||
|
value={door.transomHeight ?? 0}
|
||||||
|
onChange={(transomHeight) => patch({ transomHeight })}
|
||||||
|
step={0.01}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
<FieldRow label={t("resources.field.defaultWidth")}>
|
<FieldRow label={t("resources.field.defaultWidth")}>
|
||||||
<NumberField
|
<NumberField
|
||||||
value={door.defaultWidth}
|
value={door.defaultWidth}
|
||||||
@@ -2241,6 +2293,15 @@ function WindowStylesTab({
|
|||||||
max={4}
|
max={4}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
<FieldRow label={t("resources.field.mullionRows")}>
|
||||||
|
<NumberField
|
||||||
|
value={win.mullionRows ?? 1}
|
||||||
|
onChange={(mullionRows) => patch({ mullionRows: Math.max(1, Math.round(mullionRows)) })}
|
||||||
|
step={1}
|
||||||
|
min={1}
|
||||||
|
max={4}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
<FieldRow label={t("resources.field.glazing")}>
|
<FieldRow label={t("resources.field.glazing")}>
|
||||||
<SelectField
|
<SelectField
|
||||||
value={win.glazing}
|
value={win.glazing}
|
||||||
@@ -2264,6 +2325,39 @@ function WindowStylesTab({
|
|||||||
min={0}
|
min={0}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
<FieldRow label={t("resources.field.frameWidth")}>
|
||||||
|
<NumberField
|
||||||
|
value={win.frameWidth ?? 0.06}
|
||||||
|
onChange={(frameWidth) => patch({ frameWidth })}
|
||||||
|
step={0.005}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
<FieldRow label={t("resources.field.insetFromFace")}>
|
||||||
|
<NumberField
|
||||||
|
value={win.insetFromFace ?? 0}
|
||||||
|
onChange={(insetFromFace) => patch({ insetFromFace })}
|
||||||
|
step={0.01}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
{(win.insetFromFace ?? 0) > 0 && (
|
||||||
|
<FieldRow label={t("resources.field.insetFace")}>
|
||||||
|
<SelectField
|
||||||
|
value={win.insetFace ?? "aussen"}
|
||||||
|
onChange={(insetFace) => patch({ insetFace })}
|
||||||
|
options={INSET_FACE_OPTIONS.map((o) => ({ value: o.value, label: t(o.labelKey) }))}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
)}
|
||||||
|
<FieldRow label={t("resources.field.transomHeight")}>
|
||||||
|
<NumberField
|
||||||
|
value={win.transomHeight ?? 0}
|
||||||
|
onChange={(transomHeight) => patch({ transomHeight })}
|
||||||
|
step={0.01}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
<FieldRow label={t("resources.field.defaultSillHeight")}>
|
<FieldRow label={t("resources.field.defaultSillHeight")}>
|
||||||
<NumberField
|
<NumberField
|
||||||
value={win.defaultSillHeight}
|
value={win.defaultSillHeight}
|
||||||
|
|||||||
Reference in New Issue
Block a user