Fenster-Grundriss: Sims/Stulp folgen der eingezogenen Rahmenkante, mittel ohne Glaslinie
Sims (Fensterbank) und Anschlag-Kerben nutzten pauschal die volle Wandfläche statt der tatsächlichen (ggf. per insetFromFace eingezogenen) Rahmen- Aussenkante — bei rückversetzten Rahmen sass die Fensterbank dadurch sichtbar falsch. Stulp-Marken nutzen jetzt die bereits korrekt (tiefenbewusst) in windowSymbol berechneten meetingMarks statt einer zweiten, abweichenden Neuberechnung. Ausserdem: SIA fig. 37 (1:50) zeigt noch keine Glaslinie, nur Rahmen + Stulp-Quadrat — die kommt gemäss fig. 38 erst bei 1:20 dazu.
This commit is contained in:
@@ -446,11 +446,16 @@ export function windowSymbol(
|
|||||||
const clearTo = seg.to - fw;
|
const clearTo = seg.to - fw;
|
||||||
if (clearTo - clearFrom <= 1e-6) continue;
|
if (clearTo - clearFrom <= 1e-6) continue;
|
||||||
const operable = !!seg.sash.opening && seg.sash.opening !== "fest";
|
const operable = !!seg.sash.opening && seg.sash.opening !== "fest";
|
||||||
|
// „mittel" (SIA fig. 37) zeigt NUR Blendrahmen + Pfosten/Stulp — noch KEINE
|
||||||
|
// Glaslinie/Flügelrahmen (Nutzer-Vorgabe: „im 1:50 muss man die Fenster-
|
||||||
|
// linie noch nicht sehen, nur die Vierecke aussen/innen"). Das kommt erst
|
||||||
|
// bei „fein" (SIA fig. 38) dazu.
|
||||||
|
if (!fein) continue;
|
||||||
const gOuterN = frameOuterN - (operable ? reveal : 0);
|
const gOuterN = frameOuterN - (operable ? reveal : 0);
|
||||||
const gInnerN = frameInnerN + (operable ? reveal : 0);
|
const gInnerN = frameInnerN + (operable ? reveal : 0);
|
||||||
// Verschachtelter Flügelrahmen NUR bei „fein" (SIA fig. 38); „mittel" bleibt
|
// Verschachtelter Flügelrahmen NUR bei „fein" (SIA fig. 38); „mittel" bleibt
|
||||||
// beim reinen Blendrahmen-Band (SIA fig. 37).
|
// beim reinen Blendrahmen-Band (SIA fig. 37).
|
||||||
if (fein && operable) {
|
if (operable) {
|
||||||
const sFrom = along(jambStart, jambEnd, clearFrom);
|
const sFrom = along(jambStart, jambEnd, clearFrom);
|
||||||
const sTo = along(jambStart, jambEnd, clearTo);
|
const sTo = along(jambStart, jambEnd, clearTo);
|
||||||
sashFrames.push([
|
sashFrames.push([
|
||||||
@@ -460,23 +465,19 @@ export function windowSymbol(
|
|||||||
add(sFrom, scale(n, gInnerN)),
|
add(sFrom, scale(n, gInnerN)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
const gInsetU = fein && operable ? fw * 0.6 : 0;
|
const gInsetU = operable ? fw * 0.6 : 0;
|
||||||
const gFrom = clearFrom + gInsetU;
|
const gFrom = clearFrom + gInsetU;
|
||||||
const gTo = clearTo - gInsetU;
|
const gTo = clearTo - gInsetU;
|
||||||
if (gTo - gFrom <= 1e-6) continue;
|
if (gTo - gFrom <= 1e-6) continue;
|
||||||
const gMid = (gOuterN + gInnerN) / 2;
|
const gMid = (gOuterN + gInnerN) / 2;
|
||||||
const a = along(jambStart, jambEnd, gFrom);
|
const a = along(jambStart, jambEnd, gFrom);
|
||||||
const b = along(jambStart, jambEnd, gTo);
|
const b = along(jambStart, jambEnd, gTo);
|
||||||
// fein: Glas als Doppellinie (Isolierverglasung, ~15 mm Abstand); mittel:
|
// Glas als Doppellinie (Isolierverglasung, ~15 mm Abstand) — nur „fein"
|
||||||
// eine Glaslinie mittig.
|
// erreicht diesen Punkt (mittel bricht oben früh ab, s. o.).
|
||||||
if (fein) {
|
|
||||||
const half = Math.min(0.0075, bandDepth * 0.18);
|
const half = Math.min(0.0075, bandDepth * 0.18);
|
||||||
for (const off of [gMid - half, gMid + half]) {
|
for (const off of [gMid - half, gMid + half]) {
|
||||||
glassLines.push([add(a, scale(n, off)), add(b, scale(n, off))]);
|
glassLines.push([add(a, scale(n, off)), add(b, scale(n, off))]);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
glassLines.push([add(a, scale(n, gMid)), add(b, scale(n, gMid))]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return { frame, glassLines, mullionLines, sashFrames, sashOpeningLines: [], meetingMarks };
|
return { frame, glassLines, mullionLines, sashFrames, sashOpeningLines: [], meetingMarks };
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-30
@@ -2271,26 +2271,7 @@ function addOpeningSymbol(
|
|||||||
// fein (1:20): zusätzlich Flügelrahmen, Stulp als ZWEI Rechtecke, Fensterbank
|
// fein (1:20): zusätzlich Flügelrahmen, Stulp als ZWEI Rechtecke, Fensterbank
|
||||||
// (aussen) und Anschlag-/Laibungskerben — deutlich reicher als mittel.
|
// (aussen) und Anschlag-/Laibungskerben — deutlich reicher als mittel.
|
||||||
const strokeColor = o.color ?? POCHE_STROKE;
|
const strokeColor = o.color ?? POCHE_STROKE;
|
||||||
const { u: wu, n: wn } = wallAxisFrame(wall);
|
const { n: wn } = wallAxisFrame(wall);
|
||||||
const fpw = Math.max(0.03, wt?.frameWidth ?? 0.06); // Rahmenprofil-Breite (m)
|
|
||||||
// Kleines Rechteck (Stulp/Anschlag) um `center`, Halbmasse hu (Achse)/hn (quer).
|
|
||||||
const emitStulp = (center: Vec2, hu: number, hn: number): void => {
|
|
||||||
const c1 = add(add(center, scale(wu, -hu)), scale(wn, -hn));
|
|
||||||
const c2 = add(add(center, scale(wu, hu)), scale(wn, -hn));
|
|
||||||
const c3 = add(add(center, scale(wu, hu)), scale(wn, hn));
|
|
||||||
const c4 = add(add(center, scale(wu, -hu)), scale(wn, hn));
|
|
||||||
for (const [a, b] of [[c1, c2], [c2, c3], [c3, c4], [c4, c1]] as [Vec2, Vec2][]) {
|
|
||||||
out.push({ kind: "line", a, b, cls: "window-stulp", weightMm: SYMBOL_HAIRLINE_MM, color: strokeColor, greyed, openingId: o.id });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Flügel-Junctions (Stulp-/Anschlagstelle zweier Flügel) = Mitten der Pfosten-
|
|
||||||
// Trennlinien. NUR im typisierten Pfad (`spec`) — der Alt-Pfad (reines
|
|
||||||
// `wingCount`) hat keine echte Flügeltabelle, seine Trennlinien sind nur eine
|
|
||||||
// gleichmässige Näherung ohne Stoss-Semantik (siehe windowSymbol-Kommentar).
|
|
||||||
// Bei einflügligen Fenstern leer ⇒ kein Mittelquadrat (korrekt).
|
|
||||||
const junctionCenters: Vec2[] = spec
|
|
||||||
? sym.mullionLines.map(([a, b]) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (detail === "grob") {
|
if (detail === "grob") {
|
||||||
// SIA 1:100 — nur eine dünne Glaslinie mittig über die weisse Öffnung.
|
// SIA 1:100 — nur eine dünne Glaslinie mittig über die weisse Öffnung.
|
||||||
@@ -2324,13 +2305,11 @@ function addOpeningSymbol(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Stulp-Mittelquadrat je Flügel-Junction (SIA-Signatur): mittel = EIN Quadrat,
|
// Stulp-Mittelquadrat je Flügel-Junction (SIA-Signatur): mittel = EIN Quadrat,
|
||||||
// fein = ZWEI anliegende Rechtecke (die beiden Stulp-Stäbe der Flügel).
|
// fein = ZWEI anliegende Rechtecke (die beiden Stulp-Stäbe der Flügel) —
|
||||||
for (const c of junctionCenters) {
|
// bereits tiefenkorrekt (inset-/insetFace-bewusst) in windowSymbol berechnet.
|
||||||
if (detail === "fein") {
|
for (const mark of sym.meetingMarks) {
|
||||||
emitStulp(add(c, scale(wu, -fpw / 2)), fpw / 2, fpw / 2);
|
for (let i = 0; i < mark.length; i++) {
|
||||||
emitStulp(add(c, scale(wu, fpw / 2)), fpw / 2, fpw / 2);
|
out.push({ kind: "line", a: mark[i], b: mark[(i + 1) % mark.length], cls: "window-stulp", weightMm: SYMBOL_HAIRLINE_MM, color: strokeColor, greyed, openingId: o.id });
|
||||||
} else {
|
|
||||||
emitStulp(c, fpw / 2, fpw / 2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Glaslinien (Anzahl je Detailgrad/Verglasung, s. glassCount).
|
// Glaslinien (Anzahl je Detailgrad/Verglasung, s. glassCount).
|
||||||
@@ -2345,9 +2324,10 @@ function addOpeningSymbol(
|
|||||||
const jb = openingJambs(wall, o);
|
const jb = openingJambs(wall, o);
|
||||||
if (jb) {
|
if (jb) {
|
||||||
const { jambStart, jambEnd } = jb;
|
const { jambStart, jambEnd } = jb;
|
||||||
const total = wallTypeThickness(getWallType(project, wall));
|
// Tatsächliche Rahmen-Aussenkante (NICHT pauschal die volle Wandfläche —
|
||||||
const refOff = wallReferenceOffset(wall, total);
|
// ein eingezogener Rahmen, s. {@link WindowType.insetFromFace}, hat seine
|
||||||
const outerN = refOff + total / 2;
|
// Aussenkante weiter innen; sym.frame[0]/[1] tragen das bereits korrekt).
|
||||||
|
const outerN = dot(sub(sym.frame[0], jambStart), wn);
|
||||||
// Fensterbank: schmales Rechteck vor der Aussenfläche, seitlich ~3 cm
|
// Fensterbank: schmales Rechteck vor der Aussenfläche, seitlich ~3 cm
|
||||||
// überstehend, ~4 cm auskragend (SIA-Sims-Andeutung im Grundriss).
|
// überstehend, ~4 cm auskragend (SIA-Sims-Andeutung im Grundriss).
|
||||||
const ov = 0.03;
|
const ov = 0.03;
|
||||||
|
|||||||
@@ -178,13 +178,13 @@ describe("generatePlan — Verglasung glazingPanes (Item 2)", () => {
|
|||||||
const glassLineCount = (p: Project, detail: "grob" | "mittel" | "fein" = "mittel") =>
|
const glassLineCount = (p: Project, detail: "grob" | "mittel" | "fein" = "mittel") =>
|
||||||
linesOfClass(p, "window-glass", detail).length;
|
linesOfClass(p, "window-glass", detail).length;
|
||||||
|
|
||||||
it("glazingPanes beeinflusst die 2D-Glaslinienzahl NICHT mehr — SIA-Detailgrad entscheidet (Doppellinie/IV nur bei 'fein', unabhängig von der Scheibenzahl)", () => {
|
it("glazingPanes beeinflusst die 2D-Glaslinienzahl NICHT mehr — SIA-Detailgrad entscheidet (mittel zeigt noch KEINE Glaslinie, SIA fig. 37; Doppellinie/IV erst bei 'fein', unabhängig von der Scheibenzahl)", () => {
|
||||||
const wt3: WindowType = { ...baseWindowType, id: "wt3", glazingPanes: 3 };
|
const wt3: WindowType = { ...baseWindowType, id: "wt3", glazingPanes: 3 };
|
||||||
const wt1: WindowType = { ...baseWindowType, id: "wt1", glazingPanes: 1 };
|
const wt1: WindowType = { ...baseWindowType, id: "wt1", glazingPanes: 1 };
|
||||||
const p3 = project({ ...baseWindow, typeId: "wt3" }, [wt3]);
|
const p3 = project({ ...baseWindow, typeId: "wt3" }, [wt3]);
|
||||||
const p1 = project({ ...baseWindow, typeId: "wt1" }, [wt1]);
|
const p1 = project({ ...baseWindow, typeId: "wt1" }, [wt1]);
|
||||||
expect(glassLineCount(p3, "mittel")).toBe(1);
|
expect(glassLineCount(p3, "mittel")).toBe(0);
|
||||||
expect(glassLineCount(p1, "mittel")).toBe(1);
|
expect(glassLineCount(p1, "mittel")).toBe(0);
|
||||||
expect(glassLineCount(p3, "fein")).toBe(2);
|
expect(glassLineCount(p3, "fein")).toBe(2);
|
||||||
expect(glassLineCount(p1, "fein")).toBe(2);
|
expect(glassLineCount(p1, "fein")).toBe(2);
|
||||||
});
|
});
|
||||||
@@ -242,6 +242,32 @@ describe("generatePlan — Stulp-/Flügelstoss-Marken (SIA fig. 37/38, window-st
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("generatePlan — Fensterbank (window-sill) folgt der eingezogenen Rahmenkante", () => {
|
||||||
|
// Wand 0.3 m dick, Achse mittig (refOff=0) -> volle Wandfläche aussen wäre bei
|
||||||
|
// n=+0.15. Ein um 0.05 m eingezogener Rahmen (insetFromFace) sitzt aber bei
|
||||||
|
// n=+0.10 (halfT − inset) — die Fensterbank muss DORT ansetzen, nicht an der
|
||||||
|
// vollen Wandfläche (Regression für den Nutzer-Report "Sims sitzt falsch").
|
||||||
|
it("Fensterbank sitzt an der eingezogenen Rahmen-Aussenkante, nicht an der vollen Wandfläche", () => {
|
||||||
|
const wt: WindowType = {
|
||||||
|
...baseWindowType,
|
||||||
|
sashes: [{ kind: "fluegel", autoWidth: true, opening: "fest" }],
|
||||||
|
insetFromFace: 0.05,
|
||||||
|
insetFace: "aussen",
|
||||||
|
frameDepth: 0.07,
|
||||||
|
};
|
||||||
|
const p = project({ ...baseWindow, typeId: "wt1" }, [wt]);
|
||||||
|
const sill = linesOfClass(p, "window-sill", "fein");
|
||||||
|
expect(sill.length).toBe(4);
|
||||||
|
const ys = sill
|
||||||
|
.flatMap((pr) => (pr.kind === "line" ? [pr.a.y, pr.b.y] : []))
|
||||||
|
.map((y) => Math.round(y * 1000) / 1000);
|
||||||
|
// Näher Rand der Fensterbank = Rahmen-Aussenkante (0.15 − 0.05 = 0.10),
|
||||||
|
// NICHT die volle Wandfläche (0.15 — der alte, fehlerhafte Wert).
|
||||||
|
expect(Math.min(...ys)).toBeCloseTo(0.1, 6);
|
||||||
|
expect(Math.min(...ys)).not.toBeCloseTo(0.15, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("generatePlan — Rollladenkasten shading (Item 3)", () => {
|
describe("generatePlan — Rollladenkasten shading (Item 3)", () => {
|
||||||
const shadingLineCount = (p: Project) => linesOfClass(p, "window-shading").length;
|
const shadingLineCount = (p: Project) => linesOfClass(p, "window-shading").length;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user