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:
@@ -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);
|
||||||
|
|||||||
+59
-52
@@ -1980,61 +1980,68 @@ 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;
|
||||||
|
|
||||||
let bandLo: number;
|
// Rahmen-Rechteck NUR bei Türen zeichnen. Beim Fenster stellt bereits
|
||||||
let bandHi: number;
|
// `windowSymbol` den Rahmen dar (voller Öffnungsumriss + Glaslinien); ein
|
||||||
let jStart: Vec2;
|
// zusätzliches Band ergäbe ein doppeltes, verschachteltes Rechteck — genau
|
||||||
let jEnd: Vec2;
|
// das wirkte im 2D „nicht korrekt". Die Tür hat dort nur Blatt+Bogen, daher
|
||||||
let weightMm: number;
|
// ist ihr Rahmenband die einzige Rahmenkontur und bleibt.
|
||||||
if (isBlock) {
|
if (o.kind === "door") {
|
||||||
// Blockrahmen: volle Öffnungsbreite (kein Pfosten-Einzug), nur eine
|
const frameWidth = Math.max(0, spec.frameWidth);
|
||||||
// Tiefenscheibe von frameWidth ab der Nah-Fläche — sitzt proud VOR/auf der
|
const isBlock = spec.frameKind === "blockrahmen";
|
||||||
// Laibung, statt schmal darin zu verschwinden (Zargen-Optik).
|
let bandLo: number;
|
||||||
const depth = Math.min(frameWidth, hi - lo);
|
let bandHi: number;
|
||||||
bandLo = nearIsAussen ? hi - depth : lo;
|
let jStart: Vec2;
|
||||||
bandHi = nearIsAussen ? hi : lo + depth;
|
let jEnd: Vec2;
|
||||||
jStart = jambStart;
|
let weightMm: number;
|
||||||
jEnd = jambEnd;
|
if (isBlock) {
|
||||||
weightMm = SYMBOL_HAIRLINE_MM * 2.2;
|
// Blockrahmen: volle Öffnungsbreite (kein Pfosten-Einzug), nur eine
|
||||||
} else {
|
// Tiefenscheibe von frameWidth ab der Nah-Fläche — sitzt proud VOR/auf der
|
||||||
// Zarge: ringsum um frameWidth von der vollen Öffnung eingezogenes,
|
// Laibung, statt schmal darin zu verschwinden (Zargen-Optik).
|
||||||
// schmales Rahmenband (Laibungs-Anschlaglinie).
|
const depth = Math.min(frameWidth, hi - lo);
|
||||||
const jambSpan = Math.hypot(jambEnd.x - jambStart.x, jambEnd.y - jambStart.y);
|
bandLo = nearIsAussen ? hi - depth : lo;
|
||||||
const jambInset = Math.min(frameWidth, jambSpan / 2);
|
bandHi = nearIsAussen ? hi : lo + depth;
|
||||||
jStart = along(jambStart, jambEnd, jambInset);
|
jStart = jambStart;
|
||||||
jEnd = along(jambEnd, jambStart, jambInset);
|
jEnd = jambEnd;
|
||||||
const depthInset = Math.min(frameWidth, (hi - lo) / 2);
|
weightMm = SYMBOL_HAIRLINE_MM * 2.2;
|
||||||
bandLo = lo + depthInset;
|
} else {
|
||||||
bandHi = hi - depthInset;
|
// Zarge: ringsum um frameWidth von der vollen Öffnung eingezogenes,
|
||||||
weightMm = SYMBOL_HAIRLINE_MM;
|
// schmales Rahmenband (Laibungs-Anschlaglinie).
|
||||||
}
|
const jambSpan = Math.hypot(jambEnd.x - jambStart.x, jambEnd.y - jambStart.y);
|
||||||
|
const jambInset = Math.min(frameWidth, jambSpan / 2);
|
||||||
|
jStart = along(jambStart, jambEnd, jambInset);
|
||||||
|
jEnd = along(jambEnd, jambStart, jambInset);
|
||||||
|
const depthInset = Math.min(frameWidth, (hi - lo) / 2);
|
||||||
|
bandLo = lo + depthInset;
|
||||||
|
bandHi = hi - depthInset;
|
||||||
|
weightMm = SYMBOL_HAIRLINE_MM;
|
||||||
|
}
|
||||||
|
|
||||||
// Geschlossenes Rechteck aus 4 Kanten (Primitive "line", nicht "polygon" —
|
// Geschlossenes Rechteck aus 4 Kanten (Primitive "line", nicht "polygon" —
|
||||||
// so bleibt das Band per `cls` selektierbar/testbar wie die übrigen Symbole).
|
// so bleibt das Band per `cls` selektierbar/testbar wie die übrigen Symbole).
|
||||||
const p1 = add(jStart, scale(n, bandLo));
|
const p1 = add(jStart, scale(n, bandLo));
|
||||||
const p2 = add(jEnd, scale(n, bandLo));
|
const p2 = add(jEnd, scale(n, bandLo));
|
||||||
const p3 = add(jEnd, scale(n, bandHi));
|
const p3 = add(jEnd, scale(n, bandHi));
|
||||||
const p4 = add(jStart, scale(n, bandHi));
|
const p4 = add(jStart, scale(n, bandHi));
|
||||||
const edges: [Vec2, Vec2][] = [
|
const edges: [Vec2, Vec2][] = [
|
||||||
[p1, p2],
|
[p1, p2],
|
||||||
[p2, p3],
|
[p2, p3],
|
||||||
[p3, p4],
|
[p3, p4],
|
||||||
[p4, p1],
|
[p4, p1],
|
||||||
];
|
];
|
||||||
for (const [a, b] of edges) {
|
for (const [a, b] of edges) {
|
||||||
out.push({
|
out.push({
|
||||||
kind: "line",
|
kind: "line",
|
||||||
a,
|
a,
|
||||||
b,
|
b,
|
||||||
cls: "opening-frame",
|
cls: "opening-frame",
|
||||||
weightMm,
|
weightMm,
|
||||||
color,
|
color,
|
||||||
greyed,
|
greyed,
|
||||||
openingId: o.id,
|
openingId: o.id,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Oberlicht (Überkopf-Andeutung): eine gestrichelte Linie quer über die
|
// Oberlicht (Überkopf-Andeutung): eine gestrichelte Linie quer über die
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user