Fenster: echte Sprossen-Spalten (mullionCols) analog Kämpfer-Zeilen
Vertikale Sprossenteilung im Glasfeld (mullionCols−1 Spalten), spiegelbildlich zur bestehenden mullionRows-Logik: 3D-Rahmen-Riegel, Ansichts-Trennlinien + Glasscheiben als echtes rows×cols-Raster, Eingabefeld im Fenstertyp-Editor und im ResourceManager.
This commit is contained in:
@@ -408,6 +408,7 @@ export const de = {
|
|||||||
"oed.f.frameWidth": "Rahmenbreite Ansicht (m)",
|
"oed.f.frameWidth": "Rahmenbreite Ansicht (m)",
|
||||||
"oed.f.transomHeight": "Oberlichthöhe (m)",
|
"oed.f.transomHeight": "Oberlichthöhe (m)",
|
||||||
"oed.f.mullionRows": "Kämpfer-/Sprossenzeilen",
|
"oed.f.mullionRows": "Kämpfer-/Sprossenzeilen",
|
||||||
|
"oed.f.mullionCols": "Sprossen-Spalten",
|
||||||
"oed.kind.dreh": "Dreh",
|
"oed.kind.dreh": "Dreh",
|
||||||
"oed.kind.kipp": "Kipp",
|
"oed.kind.kipp": "Kipp",
|
||||||
"oed.kind.drehkipp": "Dreh-Kipp",
|
"oed.kind.drehkipp": "Dreh-Kipp",
|
||||||
|
|||||||
@@ -408,6 +408,7 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"oed.f.frameWidth": "Frame face width (m)",
|
"oed.f.frameWidth": "Frame face width (m)",
|
||||||
"oed.f.transomHeight": "Transom height (m)",
|
"oed.f.transomHeight": "Transom height (m)",
|
||||||
"oed.f.mullionRows": "Transom/muntin rows",
|
"oed.f.mullionRows": "Transom/muntin rows",
|
||||||
|
"oed.f.mullionCols": "Muntin columns",
|
||||||
"oed.kind.dreh": "Casement",
|
"oed.kind.dreh": "Casement",
|
||||||
"oed.kind.kipp": "Tilt",
|
"oed.kind.kipp": "Tilt",
|
||||||
"oed.kind.drehkipp": "Turn-tilt",
|
"oed.kind.drehkipp": "Turn-tilt",
|
||||||
|
|||||||
@@ -427,6 +427,12 @@ export interface WindowType {
|
|||||||
* Sprossen-/Flügelraster. Fehlt es, gilt 1.
|
* Sprossen-/Flügelraster. Fehlt es, gilt 1.
|
||||||
*/
|
*/
|
||||||
mullionRows?: number;
|
mullionRows?: number;
|
||||||
|
/**
|
||||||
|
* Anzahl vertikaler Felder (Sprossen-Spalten): 1 = keine Querteilung, 2 = ein
|
||||||
|
* Sprossen-Mittelpunkt (Spalte), usw. Zusammen mit {@link wingCount} (Spalten)
|
||||||
|
* ergibt sich das Sprossen-/Flügelraster. Fehlt es, gilt 1.
|
||||||
|
*/
|
||||||
|
mullionCols?: 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). */
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ describe("toElevation — Öffnungen: Verdeckung + Reveal-Schatten", () => {
|
|||||||
|
|
||||||
it("Kämpfer-Zeilen (mullionRows): teilen das Glasfeld horizontal (Glas + Trennlinien)", () => {
|
it("Kämpfer-Zeilen (mullionRows): teilen das Glasfeld horizontal (Glas + Trennlinien)", () => {
|
||||||
const base = elevationProject({ overhang: false });
|
const base = elevationProject({ overhang: false });
|
||||||
const mk = (mullionRows: number): Project =>
|
const mk = (mullionRows: number): Project =>
|
||||||
({
|
({
|
||||||
...base,
|
...base,
|
||||||
windowTypes: [
|
windowTypes: [
|
||||||
@@ -444,6 +444,52 @@ describe("toElevation — Öffnungen: Verdeckung + Reveal-Schatten", () => {
|
|||||||
expect(glassCount(3)).toBe(3); // in 3 Zeilen geteilt
|
expect(glassCount(3)).toBe(3); // in 3 Zeilen geteilt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Sprossen-Spalten (mullionCols): teilen das Glasfeld vertikal (Glas + Trennlinien)", () => {
|
||||||
|
const base = elevationProject({ overhang: false });
|
||||||
|
const mk = (mullionCols: number): Project =>
|
||||||
|
({
|
||||||
|
...base,
|
||||||
|
windowTypes: [
|
||||||
|
{
|
||||||
|
id: "wt",
|
||||||
|
name: "F",
|
||||||
|
kind: "fest",
|
||||||
|
wingCount: 1,
|
||||||
|
glazing: "einfach",
|
||||||
|
frameWidth: 0.06,
|
||||||
|
mullionCols,
|
||||||
|
defaultSillHeight: 0.9,
|
||||||
|
defaultWidth: 1,
|
||||||
|
defaultHeight: 1.2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
openings: [
|
||||||
|
{
|
||||||
|
id: "op",
|
||||||
|
type: "opening",
|
||||||
|
hostWallId: "NEAR",
|
||||||
|
categoryCode: "21",
|
||||||
|
kind: "window",
|
||||||
|
typeId: "wt",
|
||||||
|
position: 1.5,
|
||||||
|
width: 1,
|
||||||
|
height: 1.2,
|
||||||
|
sillHeight: 0.9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}) as unknown as Project;
|
||||||
|
const glassCount = (cols: number): number => {
|
||||||
|
const p = mk(cols);
|
||||||
|
const plan = generateElevationPlan(p, p.drawingLevels[1])!;
|
||||||
|
// Glasflächen = weiss/blau gefüllte Polygone mit Kontur, keine Öffnung/Rahmen.
|
||||||
|
return plan.primitives.filter(
|
||||||
|
(pr) => pr.kind === "polygon" && pr.fill === "#9fc2d9",
|
||||||
|
).length;
|
||||||
|
};
|
||||||
|
expect(glassCount(1)).toBe(1); // fest, 1 Feld
|
||||||
|
expect(glassCount(3)).toBe(3); // in 3 Spalten geteilt
|
||||||
|
});
|
||||||
|
|
||||||
it("Reveal-Schatten: shadows=true erzeugt Öffnungs-Schattenbänder, false keine", () => {
|
it("Reveal-Schatten: shadows=true erzeugt Öffnungs-Schattenbänder, false keine", () => {
|
||||||
const project = openingProject();
|
const project = openingProject();
|
||||||
const level = project.drawingLevels[1];
|
const level = project.drawingLevels[1];
|
||||||
|
|||||||
+17
-1
@@ -776,16 +776,32 @@ function collectOpenings(project: Project, frame: ElevationFrame): ProjectedOpen
|
|||||||
// horizontale Scheiben geteilt (analog zum 3D, s. toWalls3d), mit einer
|
// horizontale Scheiben geteilt (analog zum 3D, s. toWalls3d), mit einer
|
||||||
// Trennlinie je Zeilengrenze über die Glasbreite. 1 = ungeteilt (bisher).
|
// Trennlinie je Zeilengrenze über die Glasbreite. 1 = ungeteilt (bisher).
|
||||||
const rows = Math.max(1, Math.round(wt?.mullionRows ?? 1));
|
const rows = Math.max(1, Math.round(wt?.mullionRows ?? 1));
|
||||||
|
// Sprossen-Spalten (mullionCols): das Glasfeld wird in `cols` vertikale
|
||||||
|
// Scheiben geteilt (analog zum 3D, s. toWalls3d), mit einer
|
||||||
|
// Trennlinie je Spaltengrenze über die Glasscheibenhöhe.
|
||||||
|
const cols = Math.max(1, Math.round(wt?.mullionCols ?? 1));
|
||||||
if (gB - gA > 1e-6 && gz1 - gz0 > 1e-6) {
|
if (gB - gA > 1e-6 && gz1 - gz0 > 1e-6) {
|
||||||
|
// Glasfelder: rows × cols Gitter aus Einzelscheiben.
|
||||||
for (let r = 0; r < rows; r++) {
|
for (let r = 0; r < rows; r++) {
|
||||||
const rz0 = gz0 + ((gz1 - gz0) * r) / rows + (r > 0 ? gV / 2 : 0);
|
const rz0 = gz0 + ((gz1 - gz0) * r) / rows + (r > 0 ? gV / 2 : 0);
|
||||||
const rz1 = gz0 + ((gz1 - gz0) * (r + 1)) / rows - (r < rows - 1 ? gV / 2 : 0);
|
const rz1 = gz0 + ((gz1 - gz0) * (r + 1)) / rows - (r < rows - 1 ? gV / 2 : 0);
|
||||||
if (rz1 - rz0 > 1e-6) pr.glass.push(rect(pr.pts, gA, gB, rz0, rz1));
|
if (rz1 - rz0 <= 1e-6) continue;
|
||||||
|
for (let c = 0; c < cols; c++) {
|
||||||
|
const ca0 = gA + ((gB - gA) * c) / cols + (c > 0 ? gU / 2 : 0);
|
||||||
|
const ca1 = gA + ((gB - gA) * (c + 1)) / cols - (c < cols - 1 ? gU / 2 : 0);
|
||||||
|
if (ca1 - ca0 > 1e-6) pr.glass.push(rect(pr.pts, ca0, ca1, rz0, rz1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Horizontale Trennlinien (Kämpfer)
|
||||||
for (let r = 1; r < rows; r++) {
|
for (let r = 1; r < rows; r++) {
|
||||||
const rz = gz0 + ((gz1 - gz0) * r) / rows;
|
const rz = gz0 + ((gz1 - gz0) * r) / rows;
|
||||||
pr.divisions.push([quadAt(pr.pts, gA, rz), quadAt(pr.pts, gB, rz)]);
|
pr.divisions.push([quadAt(pr.pts, gA, rz), quadAt(pr.pts, gB, rz)]);
|
||||||
}
|
}
|
||||||
|
// Vertikale Trennlinien (Sprossen-Spalten)
|
||||||
|
for (let c = 1; c < cols; c++) {
|
||||||
|
const ca = gA + ((gB - gA) * c) / cols;
|
||||||
|
pr.divisions.push([quadAt(pr.pts, ca, gz0), quadAt(pr.pts, ca, gz1)]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Trennlinie zum nächsten Feld (ausser am Rand).
|
// Trennlinie zum nächsten Feld (ausser am Rand).
|
||||||
if (fa1 < 1 - fa - 1e-6) {
|
if (fa1 < 1 - fa - 1e-6) {
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ describe("Rahmen-/Sprossen-Riegel typisierter Öffnungen (emitOpeningFrames)", (
|
|||||||
...(sampleProject.windowTypes ?? []),
|
...(sampleProject.windowTypes ?? []),
|
||||||
{
|
{
|
||||||
id: "win-mullion-test",
|
id: "win-mullion-test",
|
||||||
name: "Test Sprossen",
|
name: "Test Fenster 2-flg",
|
||||||
kind: "fest",
|
kind: "fest",
|
||||||
wingCount: 2,
|
wingCount: 2,
|
||||||
mullionRows: 2,
|
mullionRows: 2,
|
||||||
@@ -541,7 +541,7 @@ describe("Rahmen-/Sprossen-Riegel typisierter Öffnungen (emitOpeningFrames)", (
|
|||||||
],
|
],
|
||||||
openings: [
|
openings: [
|
||||||
{
|
{
|
||||||
id: "OM",
|
id: "OB",
|
||||||
type: "opening",
|
type: "opening",
|
||||||
hostWallId: "W1",
|
hostWallId: "W1",
|
||||||
categoryCode: "21",
|
categoryCode: "21",
|
||||||
@@ -556,10 +556,93 @@ describe("Rahmen-/Sprossen-Riegel typisierter Öffnungen (emitOpeningFrames)", (
|
|||||||
doors: [],
|
doors: [],
|
||||||
context: [],
|
context: [],
|
||||||
};
|
};
|
||||||
const baseCount = projectToModel3d(baseline).meshes.filter(isFrame).length;
|
const baselineFrames = projectToModel3d(baseline).meshes.filter(isFrame);
|
||||||
const mullionCount = projectToModel3d(withMullions).meshes.filter(isFrame).length;
|
const withMullionsFrames = projectToModel3d(withMullions).meshes.filter(isFrame);
|
||||||
// +1 Mittelpfosten (wingCount-1) +1 Kämpfer-Zeile (mullionRows-1).
|
// Mit den Mittelpfosten (wingCount-1) und Kämpfern (mullionRows-1):
|
||||||
expect(mullionCount).toBe(baseCount + 2);
|
// 1 Flügelpfosten (wingCount−1)
|
||||||
|
// 1 Kämpfer (mullionRows−1)
|
||||||
|
// → zusammen 2 Riegel mehr als Basis (1×1 =1).
|
||||||
|
expect(withMullionsFrames.length).toBe(baselineFrames.length + 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("wingCount=2 + mullionCols=2 -> je ein zusätzlicher Sprossen-Spalte", () => {
|
||||||
|
// Test um mullionCols zu prüfen (analog obigem mullionRows-Test).
|
||||||
|
const baseline: Project = {
|
||||||
|
...sampleProject,
|
||||||
|
windowTypes: [
|
||||||
|
...(sampleProject.windowTypes ?? []),
|
||||||
|
{
|
||||||
|
id: "win-fixed-base",
|
||||||
|
name: "Test Fest 1-flg",
|
||||||
|
kind: "fest",
|
||||||
|
wingCount: 1,
|
||||||
|
mullionCols: 1,
|
||||||
|
glazing: "einfach",
|
||||||
|
frameThickness: 0.06,
|
||||||
|
frameWidth: 0.06,
|
||||||
|
defaultSillHeight: 0.9,
|
||||||
|
defaultWidth: 1.0,
|
||||||
|
defaultHeight: 1.2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
openings: [
|
||||||
|
{
|
||||||
|
id: "OB",
|
||||||
|
type: "opening",
|
||||||
|
hostWallId: "W1",
|
||||||
|
categoryCode: "21",
|
||||||
|
kind: "window",
|
||||||
|
typeId: "win-fixed-base",
|
||||||
|
position: 3.0,
|
||||||
|
width: 1.0,
|
||||||
|
height: 1.2,
|
||||||
|
sillHeight: 0.9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
doors: [],
|
||||||
|
context: [],
|
||||||
|
};
|
||||||
|
const withMullions: Project = {
|
||||||
|
...sampleProject,
|
||||||
|
windowTypes: [
|
||||||
|
...(sampleProject.windowTypes ?? []),
|
||||||
|
{
|
||||||
|
id: "win-mullion-test",
|
||||||
|
name: "Test Fest 1-flg mit Sprossen-Spalte",
|
||||||
|
kind: "fest",
|
||||||
|
wingCount: 1,
|
||||||
|
mullionCols: 2,
|
||||||
|
glazing: "einfach",
|
||||||
|
frameThickness: 0.06,
|
||||||
|
frameWidth: 0.06,
|
||||||
|
defaultSillHeight: 0.9,
|
||||||
|
defaultWidth: 1.0,
|
||||||
|
defaultHeight: 1.2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
openings: [
|
||||||
|
{
|
||||||
|
id: "OB",
|
||||||
|
type: "opening",
|
||||||
|
hostWallId: "W1",
|
||||||
|
categoryCode: "21",
|
||||||
|
kind: "window",
|
||||||
|
typeId: "win-mullion-test",
|
||||||
|
position: 3.0,
|
||||||
|
width: 1.0,
|
||||||
|
height: 1.2,
|
||||||
|
sillHeight: 0.9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
doors: [],
|
||||||
|
context: [],
|
||||||
|
};
|
||||||
|
const baselineFrames = projectToModel3d(baseline).meshes.filter(isFrame);
|
||||||
|
const withMullionsFrames = projectToModel3d(withMullions).meshes.filter(isFrame);
|
||||||
|
// wingCount bleibt in beiden Fällen 1 (isoliert den mullionCols-Effekt von
|
||||||
|
// wingCount, s. separater Mittelpfosten-Test oben): nur die Sprossen-Spalte
|
||||||
|
// (mullionCols−1) macht den Unterschied -> 1 Riegel mehr als Basis.
|
||||||
|
expect(withMullionsFrames.length).toBe(baselineFrames.length + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("öffenbarer Flügel bekommt einen Flügelrahmen-Ring (4 Riegel) im 3D; fest keinen", () => {
|
it("öffenbarer Flügel bekommt einen Flügelrahmen-Ring (4 Riegel) im 3D; fest keinen", () => {
|
||||||
|
|||||||
@@ -2035,6 +2035,8 @@ interface OpeningFrameParams {
|
|||||||
wingCount: number;
|
wingCount: number;
|
||||||
/** Kämpfer-Zeilen (horizontale Teilung = mullionRows−1); Türen immer 1. */
|
/** Kämpfer-Zeilen (horizontale Teilung = mullionRows−1); Türen immer 1. */
|
||||||
mullionRows: number;
|
mullionRows: number;
|
||||||
|
/** Sprossen-Spalten (vertikale Teilung = mullionCols−1); Türen immer 1. */
|
||||||
|
mullionCols: number;
|
||||||
/** Ob eine Glasscheibe gezeichnet wird (Fenster immer, Tür nur `leafStyle: "glas"`). */
|
/** Ob eine Glasscheibe gezeichnet wird (Fenster immer, Tür nur `leafStyle: "glas"`). */
|
||||||
glazed: boolean;
|
glazed: boolean;
|
||||||
/**
|
/**
|
||||||
@@ -2094,6 +2096,7 @@ function resolveOpeningFrame(project: Project, wall: Wall, op: Opening): Opening
|
|||||||
// Zweiflüglige Tür ⇒ 2 Flügel = ein mittiger Anschlagpfosten (Stulp) im 3D.
|
// Zweiflüglige Tür ⇒ 2 Flügel = ein mittiger Anschlagpfosten (Stulp) im 3D.
|
||||||
wingCount: Math.max(1, Math.round(dt.leafCount ?? 1)),
|
wingCount: Math.max(1, Math.round(dt.leafCount ?? 1)),
|
||||||
mullionRows: 1,
|
mullionRows: 1,
|
||||||
|
mullionCols: 1,
|
||||||
glazed: dt.leafStyle === "glas",
|
glazed: dt.leafStyle === "glas",
|
||||||
glazingPanes: 1,
|
glazingPanes: 1,
|
||||||
// Teilverglasung: glazingRatio (Glasanteil oben) bei Glasblatt; sonst voll.
|
// Teilverglasung: glazingRatio (Glasanteil oben) bei Glasblatt; sonst voll.
|
||||||
@@ -2139,6 +2142,7 @@ function resolveOpeningFrame(project: Project, wall: Wall, op: Opening): Opening
|
|||||||
hasSill: true,
|
hasSill: true,
|
||||||
wingCount,
|
wingCount,
|
||||||
mullionRows: Math.max(1, Math.round(wt.mullionRows ?? 1)),
|
mullionRows: Math.max(1, Math.round(wt.mullionRows ?? 1)),
|
||||||
|
mullionCols: Math.max(1, Math.round(wt.mullionCols ?? 1)),
|
||||||
glazed: true,
|
glazed: true,
|
||||||
glazingPanes: glazingPanesOf(wt),
|
glazingPanes: glazingPanesOf(wt),
|
||||||
glazedFraction: 1,
|
glazedFraction: 1,
|
||||||
@@ -2232,6 +2236,15 @@ function frameMeshesForOpening(
|
|||||||
push(sashFrom, sashTo, center - fw / 2, center + fw / 2);
|
push(sashFrom, sashTo, center - fw / 2, center + fw / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sprossen-Spalten (vertikal, mullionCols−1) im Sash-Bereich.
|
||||||
|
if (params.mullionCols > 1 && sashTo - sashFrom > EPS) {
|
||||||
|
const cell = (sashTo - sashFrom) / params.mullionCols;
|
||||||
|
for (let i = 1; i < params.mullionCols; i++) {
|
||||||
|
const center = sashFrom + i * cell;
|
||||||
|
push(center - fw / 2, center + fw / 2, sashBottom, sashTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Flügelrahmen je ÖFFENBAREM Flügel: ein eigener Rahmen-Riegel-Ring (links/
|
// Flügelrahmen je ÖFFENBAREM Flügel: ein eigener Rahmen-Riegel-Ring (links/
|
||||||
// rechts/oben/unten), der DIREKT am Blendrahmen ansetzt (kein Spalt — sonst
|
// rechts/oben/unten), der DIREKT am Blendrahmen ansetzt (kein Spalt — sonst
|
||||||
|
|||||||
@@ -492,6 +492,13 @@ function FluegelPanel({
|
|||||||
min={1}
|
min={1}
|
||||||
onCommit={(n) => patchWt({ mullionRows: Math.max(1, Math.round(n)) })}
|
onCommit={(n) => patchWt({ mullionRows: Math.max(1, Math.round(n)) })}
|
||||||
/>
|
/>
|
||||||
|
<NumField
|
||||||
|
label={t("oed.f.mullionCols")}
|
||||||
|
value={wt.mullionCols ?? 1}
|
||||||
|
step={1}
|
||||||
|
min={1}
|
||||||
|
onCommit={(n) => patchWt({ mullionCols: Math.max(1, Math.round(n)) })}
|
||||||
|
/>
|
||||||
<table className="oed-table">
|
<table className="oed-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -879,6 +886,28 @@ function ElevationPreview({
|
|||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
})()}
|
})()}
|
||||||
|
{/* Sprossen-Spalten (mullionCols − 1 vertikale Teiler im
|
||||||
|
Hauptfeld unterhalb eines etwaigen Oberlichts). */}
|
||||||
|
{!dt &&
|
||||||
|
wt &&
|
||||||
|
(() => {
|
||||||
|
const cols = Math.max(1, Math.round(wt.mullionCols ?? 1));
|
||||||
|
if (cols <= 1) return null;
|
||||||
|
const top = originY + px(wt.transomHeight ?? 0);
|
||||||
|
const fieldH = px(h) - px(wt.transomHeight ?? 0);
|
||||||
|
const fieldW = px(w);
|
||||||
|
return Array.from({ length: cols - 1 }, (_, k) => (
|
||||||
|
<line
|
||||||
|
key={k}
|
||||||
|
x1={pad + (fieldW * (k + 1)) / cols}
|
||||||
|
y1={top}
|
||||||
|
x2={pad + (fieldW * (k + 1)) / cols}
|
||||||
|
y2={top + fieldH}
|
||||||
|
stroke={strokeMuted}
|
||||||
|
strokeWidth={0.75}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
})()}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -2336,6 +2336,15 @@ function WindowStylesTab({
|
|||||||
max={4}
|
max={4}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
<FieldRow label={t("resources.field.mullionCols")}>
|
||||||
|
<NumberField
|
||||||
|
value={win.mullionCols ?? 1}
|
||||||
|
onChange={(mullionCols) => patch({ mullionCols: Math.max(1, Math.round(mullionCols)) })}
|
||||||
|
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}
|
||||||
|
|||||||
Reference in New Issue
Block a user