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;
|
||||
if (clearTo - clearFrom <= 1e-6) continue;
|
||||
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 gInnerN = frameInnerN + (operable ? reveal : 0);
|
||||
// Verschachtelter Flügelrahmen NUR bei „fein" (SIA fig. 38); „mittel" bleibt
|
||||
// beim reinen Blendrahmen-Band (SIA fig. 37).
|
||||
if (fein && operable) {
|
||||
if (operable) {
|
||||
const sFrom = along(jambStart, jambEnd, clearFrom);
|
||||
const sTo = along(jambStart, jambEnd, clearTo);
|
||||
sashFrames.push([
|
||||
@@ -460,23 +465,19 @@ export function windowSymbol(
|
||||
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 gTo = clearTo - gInsetU;
|
||||
if (gTo - gFrom <= 1e-6) continue;
|
||||
const gMid = (gOuterN + gInnerN) / 2;
|
||||
const a = along(jambStart, jambEnd, gFrom);
|
||||
const b = along(jambStart, jambEnd, gTo);
|
||||
// fein: Glas als Doppellinie (Isolierverglasung, ~15 mm Abstand); mittel:
|
||||
// eine Glaslinie mittig.
|
||||
if (fein) {
|
||||
// Glas als Doppellinie (Isolierverglasung, ~15 mm Abstand) — nur „fein"
|
||||
// erreicht diesen Punkt (mittel bricht oben früh ab, s. o.).
|
||||
const half = Math.min(0.0075, bandDepth * 0.18);
|
||||
for (const off of [gMid - half, gMid + half]) {
|
||||
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 };
|
||||
}
|
||||
|
||||
+10
-30
@@ -2271,26 +2271,7 @@ function addOpeningSymbol(
|
||||
// fein (1:20): zusätzlich Flügelrahmen, Stulp als ZWEI Rechtecke, Fensterbank
|
||||
// (aussen) und Anschlag-/Laibungskerben — deutlich reicher als mittel.
|
||||
const strokeColor = o.color ?? POCHE_STROKE;
|
||||
const { u: wu, 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 }))
|
||||
: [];
|
||||
const { n: wn } = wallAxisFrame(wall);
|
||||
|
||||
if (detail === "grob") {
|
||||
// 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,
|
||||
// fein = ZWEI anliegende Rechtecke (die beiden Stulp-Stäbe der Flügel).
|
||||
for (const c of junctionCenters) {
|
||||
if (detail === "fein") {
|
||||
emitStulp(add(c, scale(wu, -fpw / 2)), fpw / 2, fpw / 2);
|
||||
emitStulp(add(c, scale(wu, fpw / 2)), fpw / 2, fpw / 2);
|
||||
} else {
|
||||
emitStulp(c, fpw / 2, fpw / 2);
|
||||
// fein = ZWEI anliegende Rechtecke (die beiden Stulp-Stäbe der Flügel) —
|
||||
// bereits tiefenkorrekt (inset-/insetFace-bewusst) in windowSymbol berechnet.
|
||||
for (const mark of sym.meetingMarks) {
|
||||
for (let i = 0; i < mark.length; i++) {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
// Glaslinien (Anzahl je Detailgrad/Verglasung, s. glassCount).
|
||||
@@ -2345,9 +2324,10 @@ function addOpeningSymbol(
|
||||
const jb = openingJambs(wall, o);
|
||||
if (jb) {
|
||||
const { jambStart, jambEnd } = jb;
|
||||
const total = wallTypeThickness(getWallType(project, wall));
|
||||
const refOff = wallReferenceOffset(wall, total);
|
||||
const outerN = refOff + total / 2;
|
||||
// Tatsächliche Rahmen-Aussenkante (NICHT pauschal die volle Wandfläche —
|
||||
// ein eingezogener Rahmen, s. {@link WindowType.insetFromFace}, hat seine
|
||||
// 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
|
||||
// überstehend, ~4 cm auskragend (SIA-Sims-Andeutung im Grundriss).
|
||||
const ov = 0.03;
|
||||
|
||||
@@ -178,13 +178,13 @@ describe("generatePlan — Verglasung glazingPanes (Item 2)", () => {
|
||||
const glassLineCount = (p: Project, detail: "grob" | "mittel" | "fein" = "mittel") =>
|
||||
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 wt1: WindowType = { ...baseWindowType, id: "wt1", glazingPanes: 1 };
|
||||
const p3 = project({ ...baseWindow, typeId: "wt3" }, [wt3]);
|
||||
const p1 = project({ ...baseWindow, typeId: "wt1" }, [wt1]);
|
||||
expect(glassLineCount(p3, "mittel")).toBe(1);
|
||||
expect(glassLineCount(p1, "mittel")).toBe(1);
|
||||
expect(glassLineCount(p3, "mittel")).toBe(0);
|
||||
expect(glassLineCount(p1, "mittel")).toBe(0);
|
||||
expect(glassLineCount(p3, "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)", () => {
|
||||
const shadingLineCount = (p: Project) => linesOfClass(p, "window-shading").length;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user