From da7392e7c6115eac4fd854fd02a96650a110dbd6 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) {