From 3ecb8c45dec6414b6a990d48ee93b21727f07d12 Mon Sep 17 00:00:00 2001 From: Karim Date: Sat, 11 Jul 2026 13:21:52 +0200 Subject: [PATCH] =?UTF-8?q?Ansicht:=20K=C3=A4mpfer-/Sprossen-Zeilen=20(mul?= =?UTF-8?q?lionRows)=20im=20Fenster?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die 2D-Ansicht teilte das Glasfeld nur vertikal (Flügel) und beim Oberlicht, ignorierte aber mullionRows — ein Fenster mit horizontaler Sprossenteilung sah in der Ansicht ungeteilt aus, im 3D dagegen geteilt. Jetzt splittet die Ansicht das Glasfeld je Flügel in mullionRows Zeilen mit Trennlinien, konsistent zum 3D. --- src/plan/toElevation.test.ts | 46 ++++++++++++++++++++++++++++++++++++ src/plan/toElevation.ts | 14 ++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/plan/toElevation.test.ts b/src/plan/toElevation.test.ts index 64a486f..972b850 100644 --- a/src/plan/toElevation.test.ts +++ b/src/plan/toElevation.test.ts @@ -398,6 +398,52 @@ describe("toElevation — Öffnungen: Verdeckung + Reveal-Schatten", () => { expect(symCount("drehkipp")).toBe(4); // Dreh (2) + Kipp (2) }); + it("Kämpfer-Zeilen (mullionRows): teilen das Glasfeld horizontal (Glas + Trennlinien)", () => { + const base = elevationProject({ overhang: false }); + const mk = (mullionRows: number): Project => + ({ + ...base, + windowTypes: [ + { + id: "wt", + name: "F", + kind: "fest", + wingCount: 1, + glazing: "einfach", + frameWidth: 0.06, + mullionRows, + 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 = (rows: number): number => { + const p = mk(rows); + 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 Zeilen geteilt + }); + it("Reveal-Schatten: shadows=true erzeugt Öffnungs-Schattenbänder, false keine", () => { const project = openingProject(); const level = project.drawingLevels[1]; diff --git a/src/plan/toElevation.ts b/src/plan/toElevation.ts index 2ae0d6b..bf801bd 100644 --- a/src/plan/toElevation.ts +++ b/src/plan/toElevation.ts @@ -772,8 +772,20 @@ function collectOpenings(project: Project, frame: ElevationFrame): ProjectedOpen const gB = fa1 - gU; const gz0 = fz + gV; const gz1 = fieldTop - gV; + // Kämpfer-/Sprossen-Zeilen (mullionRows): das Glasfeld wird in `rows` + // horizontale Scheiben geteilt (analog zum 3D, s. toWalls3d), mit einer + // Trennlinie je Zeilengrenze über die Glasbreite. 1 = ungeteilt (bisher). + const rows = Math.max(1, Math.round(wt?.mullionRows ?? 1)); if (gB - gA > 1e-6 && gz1 - gz0 > 1e-6) { - pr.glass.push(rect(pr.pts, gA, gB, gz0, gz1)); + for (let r = 0; r < rows; r++) { + 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); + if (rz1 - rz0 > 1e-6) pr.glass.push(rect(pr.pts, gA, gB, rz0, rz1)); + } + for (let r = 1; r < rows; r++) { + const rz = gz0 + ((gz1 - gz0) * r) / rows; + pr.divisions.push([quadAt(pr.pts, gA, rz), quadAt(pr.pts, gB, rz)]); + } } // Trennlinie zum nächsten Feld (ausser am Rand). if (fa1 < 1 - fa - 1e-6) {