Fenster: 2D-Doppelrahmen entfernt + 3D-Scheibe an die Aussenfläche

Nutzer-Feedback „Fenster im 2D nicht korrekt / im 3D zu schwach":
- 2D: das Rahmenband (opening-frame) wurde beim Fenster ZUSÄTZLICH zum
  windowSymbol-Rahmen gezeichnet → doppeltes, verschachteltes Rechteck.
  Das Band gilt jetzt nur noch für Türen (deren Symbol nur Blatt+Bogen hat);
  Fenster nutzen allein windowSymbol (Rahmen + Glaslinien). Oberlicht-Andeutung
  (opening-transom) bleibt für beide.
- 3D: die Scheibe sass mittig tief im Reveal und wirkte schwach; sie sitzt
  jetzt knapp (3 cm) hinter der Aussenfläche → klar sichtbar, Laibung zeigt
  nach innen.
Tests angepasst (Band jetzt tür-spezifisch, Einzug am Tür-Zargenband geprüft).
tsc + vitest 640 grün.
This commit is contained in:
2026-07-09 22:03:39 +02:00
parent 7b22f8fdf7
commit 89112eff9b
3 changed files with 91 additions and 75 deletions
+25 -16
View File
@@ -135,7 +135,7 @@ function linesOfClass(p: Project, cls: string, openingId?: string): LinePrim[] {
} }
describe("generatePlan — Tür-/Fenstertyp-Rahmenband (opening-frame)", () => { describe("generatePlan — Tür-/Fenstertyp-Rahmenband (opening-frame)", () => {
it("erzeugt Rahmenband-Primitive für Tür UND Fenster mit referenziertem Typ", () => { it("erzeugt das Rahmenband NUR für die Tür (Fenster: windowSymbol zeichnet den Rahmen)", () => {
const p = project( const p = project(
[ [
{ ...baseWindow, typeId: "wt1" }, { ...baseWindow, typeId: "wt1" },
@@ -143,23 +143,24 @@ describe("generatePlan — Tür-/Fenstertyp-Rahmenband (opening-frame)", () => {
], ],
{ doorTypes: [doorType], windowTypes: [windowType] }, { doorTypes: [doorType], windowTypes: [windowType] },
); );
expect(linesOfClass(p, "opening-frame").length).toBeGreaterThan(0); // Tür: ein geschlossenes Rechteck aus 4 Kanten. Fenster: KEIN Band (sonst
// Je Öffnung ein geschlossenes Rechteck aus 4 Kanten. // doppeltes, verschachteltes Rechteck über dem windowSymbol-Rahmen).
expect(linesOfClass(p, "opening-frame", "F1").length).toBe(4);
expect(linesOfClass(p, "opening-frame", "T1").length).toBe(4); expect(linesOfClass(p, "opening-frame", "T1").length).toBe(4);
expect(linesOfClass(p, "opening-frame", "F1").length).toBe(0);
}); });
it("Blockrahmen (Tür) zeichnet eine kräftigere Linie als die Zargen-Optik (Fenster)", () => { it("Blockrahmen-Tür zeichnet eine kräftigere Linie als eine Zargen-Tür", () => {
const zargeDoor: DoorType = { ...doorType, id: "dt-zarge", frameKind: "zarge" };
const p = project( const p = project(
[ [
{ ...baseWindow, typeId: "wt1" }, { ...baseDoor, id: "T1", typeId: "dt1" },
{ ...baseDoor, typeId: "dt1" }, { ...baseDoor, id: "T2", position: 3.0, typeId: "dt-zarge" },
], ],
{ doorTypes: [doorType], windowTypes: [windowType] }, { doorTypes: [doorType, zargeDoor] },
); );
const doorWeight = linesOfClass(p, "opening-frame", "T1")[0].weightMm; const blockWeight = linesOfClass(p, "opening-frame", "T1")[0].weightMm;
const windowWeight = linesOfClass(p, "opening-frame", "F1")[0].weightMm; const zargeWeight = linesOfClass(p, "opening-frame", "T2")[0].weightMm;
expect(doorWeight).toBeGreaterThan(windowWeight); expect(blockWeight).toBeGreaterThan(zargeWeight);
}); });
}); });
@@ -178,17 +179,25 @@ describe("generatePlan — Oberlicht (opening-transom)", () => {
describe("generatePlan — Schichteinzug (insetFromFace/insetFace)", () => { describe("generatePlan — Schichteinzug (insetFromFace/insetFace)", () => {
it("verschiebt die Rahmen-Nahfläche um den Einzugsbetrag, die Gegenfläche bleibt unverändert", () => { it("verschiebt die Rahmen-Nahfläche um den Einzugsbetrag, die Gegenfläche bleibt unverändert", () => {
const flushType: WindowType = { ...windowType, id: "wt-flush", insetFromFace: 0 }; // Am Tür-Zargenband geprüft (das Fenster nutzt jetzt windowSymbol statt Band).
const inset = project([{ ...baseWindow, typeId: "wt1" }], { windowTypes: [windowType] }); const insetDoor: DoorType = {
const flush = project([{ ...baseWindow, typeId: "wt-flush" }], { windowTypes: [flushType] }); ...doorType,
id: "dt-inset",
frameKind: "zarge",
insetFromFace: 0.05,
insetFace: "aussen",
};
const flushDoor: DoorType = { ...insetDoor, id: "dt-flush", insetFromFace: 0 };
const inset = project([{ ...baseDoor, typeId: "dt-inset" }], { doorTypes: [insetDoor] });
const flush = project([{ ...baseDoor, typeId: "dt-flush" }], { doorTypes: [flushDoor] });
// Die Wandachse ist horizontal (u=(1,0)) ⇒ Wandnormale n=(0,1); die // Die Wandachse ist horizontal (u=(1,0)) ⇒ Wandnormale n=(0,1); die
// y-Koordinate der Rahmenband-Punkte entspricht direkt dem Offset quer zur // y-Koordinate der Rahmenband-Punkte entspricht direkt dem Offset quer zur
// Wand. Die "aussen"-Fläche liegt bei +n — ihr Rahmenrand muss um genau // Wand. Die "aussen"-Fläche liegt bei +n — ihr Rahmenrand muss um genau
// `insetFromFace` (0.05 m) nach innen (kleineres y) rücken; die // `insetFromFace` (0.05 m) nach innen (kleineres y) rücken; die
// gegenüberliegende ("innen"-)Fläche bleibt unverändert. // gegenüberliegende ("innen"-)Fläche bleibt unverändert.
const insetYs = linesOfClass(inset, "opening-frame", "F1").flatMap((l) => [l.a.y, l.b.y]); const insetYs = linesOfClass(inset, "opening-frame", "T1").flatMap((l) => [l.a.y, l.b.y]);
const flushYs = linesOfClass(flush, "opening-frame", "F1").flatMap((l) => [l.a.y, l.b.y]); const flushYs = linesOfClass(flush, "opening-frame", "T1").flatMap((l) => [l.a.y, l.b.y]);
expect(Math.max(...flushYs) - Math.max(...insetYs)).toBeCloseTo(0.05, 9); expect(Math.max(...flushYs) - Math.max(...insetYs)).toBeCloseTo(0.05, 9);
expect(Math.min(...insetYs)).toBeCloseTo(Math.min(...flushYs), 9); expect(Math.min(...insetYs)).toBeCloseTo(Math.min(...flushYs), 9);
+9 -2
View File
@@ -1980,10 +1980,16 @@ function addOpeningFrameBand(
const hi = Math.max(faceNear, faceFar); const hi = Math.max(faceNear, faceFar);
const { jambStart, jambEnd } = jambs; const { jambStart, jambEnd } = jambs;
const frameWidth = Math.max(0, spec.frameWidth);
const isBlock = o.kind === "door" && spec.frameKind === "blockrahmen";
const color = o.color ?? POCHE_STROKE; const color = o.color ?? POCHE_STROKE;
// Rahmen-Rechteck NUR bei Türen zeichnen. Beim Fenster stellt bereits
// `windowSymbol` den Rahmen dar (voller Öffnungsumriss + Glaslinien); ein
// zusätzliches Band ergäbe ein doppeltes, verschachteltes Rechteck — genau
// das wirkte im 2D „nicht korrekt". Die Tür hat dort nur Blatt+Bogen, daher
// ist ihr Rahmenband die einzige Rahmenkontur und bleibt.
if (o.kind === "door") {
const frameWidth = Math.max(0, spec.frameWidth);
const isBlock = spec.frameKind === "blockrahmen";
let bandLo: number; let bandLo: number;
let bandHi: number; let bandHi: number;
let jStart: Vec2; let jStart: Vec2;
@@ -2036,6 +2042,7 @@ function addOpeningFrameBand(
openingId: o.id, openingId: o.id,
}); });
} }
}
// Oberlicht (Überkopf-Andeutung): eine gestrichelte Linie quer über die // Oberlicht (Überkopf-Andeutung): eine gestrichelte Linie quer über die
// Öffnung auf der Wandachse — nur wenn der Typ ein Oberlicht definiert. // Öffnung auf der Wandachse — nur wenn der Typ ein Oberlicht definiert.
+7 -7
View File
@@ -1974,13 +1974,13 @@ function glassPanesForOpening(
const sashTo = iv.to - fw; const sashTo = iv.to - fw;
const sashBottom = vert.zBottom + (params.hasSill ? fw : 0); const sashBottom = vert.zBottom + (params.hasSill ? fw : 0);
const sashTopFull = vert.zTop - fw; const sashTopFull = vert.zTop - fw;
// Die Scheibe ist eine DÜNNE Tafel mittig in der (nun tiefen) Rahmen-Laibung — // Die Scheibe ist eine DÜNNE Tafel NAHE der Aussenfläche (nMax = +n-Seite, s.
// nicht der volle Rahmen-Querschnitt, sonst wäre das „Glas" ein dicker Block // resolveFrameNormalRange) — mittig tief im Reveal wirkte sie schwach/kaum
// und die Laibung/Reveal-Tiefe unsichtbar. So liest sich das Fenster plastisch: // sichtbar. Direkt hinter der Aussenkante (3 cm zurück) liest sich das Fenster
// Rahmen zieht in die Wand, die Scheibe sitzt zurückgesetzt in der Mitte. // klar, die Laibung/Reveal-Tiefe zeigt nach innen.
const glassMid = (params.nMin + params.nMax) / 2; const glassThick = GLASS_HALF_THICK * 2;
const gN0 = glassMid - GLASS_HALF_THICK; const gN1 = params.nMax - 0.03;
const gN1 = glassMid + GLASS_HALF_THICK; const gN0 = Math.max(params.nMin, gN1 - glassThick);
const push = (zBottom: number, zTop: number): void => { const push = (zBottom: number, zTop: number): void => {
const mesh = openingAxisBox(wall, sashFrom, sashTo, zBottom, zTop, gN0, gN1, GLASS_RGB); const mesh = openingAxisBox(wall, sashFrom, sashTo, zBottom, zTop, gN0, gN1, GLASS_RGB);
if (mesh) out.push(mesh); if (mesh) out.push(mesh);