diff --git a/src/plan/toWalls3d.test.ts b/src/plan/toWalls3d.test.ts index 66ac864..1759c6d 100644 --- a/src/plan/toWalls3d.test.ts +++ b/src/plan/toWalls3d.test.ts @@ -604,6 +604,52 @@ describe("Rahmen-/Sprossen-Riegel typisierter Öffnungen (emitOpeningFrames)", ( expect(operableCount).toBe(8); // + 4 Flügelrahmen-Riegel }); + it("Glastür mit glazingRatio < 1: Glas nur im oberen Blattanteil (Teilverglasung)", () => { + const glassDoor = (glazingRatio?: number): Project => ({ + ...sampleProject, + doorTypes: [ + { + id: "dt-glass", + name: "Glastür Test", + kind: "dreh", + leafCount: 1, + leafStyle: "glas", + glazingRatio, + frameThickness: 0.06, + frameWidth: 0.06, + defaultWidth: 0.9, + defaultHeight: 2.0, + }, + ], + openings: [ + { + id: "DG", + type: "opening", + hostWallId: "W1", + categoryCode: "21", + kind: "door", + typeId: "dt-glass", + position: 3.0, + width: 0.9, + height: 2.0, + sillHeight: 0, + }, + ], + doors: [], + context: [], + }); + // Vollglas (ratio 1 / fehlt): Glas über (fast) die ganze Blatthöhe. + const full = projectToModel3d(glassDoor(1)).meshes.filter(isGlass); + expect(full.length).toBe(1); + const fullR = zRange(full[0]); + // Halbglas (ratio 0.5): Glas nur in der oberen Hälfte -> Unterkante deutlich höher. + const half = projectToModel3d(glassDoor(0.5)).meshes.filter(isGlass); + expect(half.length).toBe(1); + const halfR = zRange(half[0]); + expect(halfR.max).toBeCloseTo(fullR.max, 6); // Oberkante gleich (an OK/Sturz) + expect(halfR.min).toBeGreaterThan(fullR.min + 0.5); // Unterkante klar angehoben + }); + it("insetFromFace verschiebt Rahmen-Position entlang der Wand-Normalen gegenüber bündig (kein Einzug)", () => { // W1-Achse (0,0)->(5,0): u=(1,0) -> Normale n=(0,-1); der Quer-Versatz // bildet sich rein in der Y-Koordinate ab (y = -t). insetFromFace 0.1 muss diff --git a/src/plan/toWalls3d.ts b/src/plan/toWalls3d.ts index 09eb9ee..98de36a 100644 --- a/src/plan/toWalls3d.ts +++ b/src/plan/toWalls3d.ts @@ -1942,6 +1942,13 @@ interface OpeningFrameParams { * {@link glassPanesForOpening}. */ glazingPanes: 1 | 2 | 3; + /** + * Verglasungs-Anteil des HAUPTFELDS (0..1, von OBEN gemessen): 1 = voll + * verglast (Fenster, Ganzglastür), < 1 = nur der obere Anteil ist Glas, unten + * bleibt ein blindes Feld (z. B. Tür mit Oberglas, `DoorType.glazingRatio`). + * Fenster: immer 1. + */ + glazedFraction: number; /** * Flügeleinteilung ({@link sashesOfWindowType}) — je ÖFFENBAREM Flügel wird im * 3D ein Flügelrahmen (Rahmen-Riegel innerhalb des Blendrahmens) erzeugt @@ -1975,6 +1982,8 @@ function resolveOpeningFrame(project: Project, wall: Wall, op: Opening): Opening mullionRows: 1, glazed: dt.leafStyle === "glas", glazingPanes: 1, + // Teilverglasung: glazingRatio (Glasanteil oben) bei Glasblatt; sonst voll. + glazedFraction: Math.max(0.05, Math.min(1, dt.glazingRatio ?? 1)), sashes: [], }; } @@ -2014,6 +2023,7 @@ function resolveOpeningFrame(project: Project, wall: Wall, op: Opening): Opening mullionRows: Math.max(1, Math.round(wt.mullionRows ?? 1)), glazed: true, glazingPanes: glazingPanesOf(wt), + glazedFraction: 1, sashes, }; } @@ -2184,12 +2194,19 @@ function glassPanesForOpening( if (n1 <= params.nMin) break; } }; + // Teilverglasung (Türen mit glazingRatio < 1): das HAUPTFELD ist nur im oberen + // `glazedFraction`-Anteil Glas; darunter bleibt ein blindes Blattfeld. Von der + // OBERKANTE des Hauptbands gemessen. Fenster: glazedFraction = 1 (voll). + const frac = Math.max(0.05, Math.min(1, params.glazedFraction)); + const glazedBottom = (bBottom: number, bTop: number) => + bTop - frac * (bTop - bBottom); if (params.transomHeight > EPS) { const splitZ = clampRange(vert.zTop - params.transomHeight, sashBottom, sashTopFull); - push(sashBottom, splitZ - fw / 2); // Hauptscheibe/-blatt unterhalb des Kämpfers. - push(splitZ + fw / 2, sashTopFull); // Oberlicht-Scheibe oberhalb des Kämpfers. + const mainTop = splitZ - fw / 2; + push(glazedBottom(sashBottom, mainTop), mainTop); // Hauptglas (ggf. nur oben). + push(splitZ + fw / 2, sashTopFull); // Oberlicht-Scheibe (immer voll). } else { - push(sashBottom, sashTopFull); + push(glazedBottom(sashBottom, sashTopFull), sashTopFull); } return out; }