Fenster: konfigurierbare Auf-/Untersicht-Andeutung im Grundriss (WindowType.sillLine)
Ersatz für die entfernte pauschale Brüstungslinie: wählbar aussen/innen/beide Flächen + Blickrichtung Auf-/Untersicht (Aufsicht dünn gepunktet, Untersicht gestrichelt wie die übrigen Überkopf-Projektionen), an der tatsächlichen Rahmenkante statt der Wandachse. Default aus (kein Feld gesetzt = keine Linie). UI-Feld im Fenstertyp-Editor.
This commit is contained in:
@@ -409,6 +409,12 @@ export const de = {
|
|||||||
"oed.f.transomHeight": "Oberlichthöhe (m)",
|
"oed.f.transomHeight": "Oberlichthöhe (m)",
|
||||||
"oed.f.mullionRows": "Kämpfer-/Sprossenzeilen",
|
"oed.f.mullionRows": "Kämpfer-/Sprossenzeilen",
|
||||||
"oed.f.mullionCols": "Sprossen-Spalten",
|
"oed.f.mullionCols": "Sprossen-Spalten",
|
||||||
|
"oed.f.sillLineFaces": "Auf-/Untersicht-Linie",
|
||||||
|
"oed.f.sillLineView": "Blickrichtung",
|
||||||
|
"oed.sillLine.off": "Keine",
|
||||||
|
"oed.sillLine.beide": "Beide",
|
||||||
|
"oed.sillLine.auf": "Aufsicht (von oben)",
|
||||||
|
"oed.sillLine.unter": "Untersicht (von unten)",
|
||||||
"oed.kind.dreh": "Dreh",
|
"oed.kind.dreh": "Dreh",
|
||||||
"oed.kind.kipp": "Kipp",
|
"oed.kind.kipp": "Kipp",
|
||||||
"oed.kind.drehkipp": "Dreh-Kipp",
|
"oed.kind.drehkipp": "Dreh-Kipp",
|
||||||
|
|||||||
@@ -409,6 +409,12 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"oed.f.transomHeight": "Transom height (m)",
|
"oed.f.transomHeight": "Transom height (m)",
|
||||||
"oed.f.mullionRows": "Transom/muntin rows",
|
"oed.f.mullionRows": "Transom/muntin rows",
|
||||||
"oed.f.mullionCols": "Muntin columns",
|
"oed.f.mullionCols": "Muntin columns",
|
||||||
|
"oed.f.sillLineFaces": "Top-/bottom-view line",
|
||||||
|
"oed.f.sillLineView": "View direction",
|
||||||
|
"oed.sillLine.off": "None",
|
||||||
|
"oed.sillLine.beide": "Both",
|
||||||
|
"oed.sillLine.auf": "Top view (from above)",
|
||||||
|
"oed.sillLine.unter": "Bottom view (from below)",
|
||||||
"oed.kind.dreh": "Casement",
|
"oed.kind.dreh": "Casement",
|
||||||
"oed.kind.kipp": "Tilt",
|
"oed.kind.kipp": "Tilt",
|
||||||
"oed.kind.drehkipp": "Turn-tilt",
|
"oed.kind.drehkipp": "Turn-tilt",
|
||||||
|
|||||||
@@ -458,6 +458,15 @@ export interface WindowType {
|
|||||||
* (Meter), abgeteilt durch einen Kämpfer. 0/fehlt ⇒ kein Oberlicht.
|
* (Meter), abgeteilt durch einen Kämpfer. 0/fehlt ⇒ kein Oberlicht.
|
||||||
*/
|
*/
|
||||||
transomHeight?: number;
|
transomHeight?: number;
|
||||||
|
/**
|
||||||
|
* Auf-/Untersicht-Andeutung im Grundriss (mittel/fein): eine Referenzlinie an
|
||||||
|
* der gewählten Fläche(n) — sinnvoll, wenn die Öffnung die Schnittebene NICHT
|
||||||
|
* regulär durchschneidet (z. B. bodentiefes Fenster oder hoch sitzendes
|
||||||
|
* Oberlicht). `faces` wählt aussen/innen/beide, `view` die Blickrichtung:
|
||||||
|
* "auf" (Aufsicht von oben, dünn) oder "unter" (Untersicht von unten,
|
||||||
|
* gestrichelt wie eine überkopf liegende Kante). Fehlt/undefined ⇒ keine Linie.
|
||||||
|
*/
|
||||||
|
sillLine?: { faces: "aussen" | "innen" | "beide"; view: "auf" | "unter" };
|
||||||
/** Default-Brüstungshöhe über Wand-UK (Meter). */
|
/** Default-Brüstungshöhe über Wand-UK (Meter). */
|
||||||
defaultSillHeight: number;
|
defaultSillHeight: number;
|
||||||
/** Default-Lichtbreite (Meter). */
|
/** Default-Lichtbreite (Meter). */
|
||||||
|
|||||||
@@ -2349,10 +2349,21 @@ function addOpeningSymbol(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Die frühere Brüstungslinie (Wandachse, unabhängig von der Rahmenposition)
|
// Auf-/Untersicht-Andeutung ({@link WindowType.sillLine}): eine Referenzlinie
|
||||||
// ist entfernt (Nutzer-Feedback: falsch positioniert, Dash rendert nicht
|
// an der Rahmen-Aussen-/Innenkante (NICHT der Wandachse — ersetzt die frühere,
|
||||||
// sichtbar) — eine korrekte Andeutung kommt als Teil der geplanten Auf-/
|
// falsch positionierte Brüstungslinie), nur wenn der Typ es explizit wählt.
|
||||||
// Untersicht-Option (aussen/innen, je nach Einbaulage) neu.
|
// "auf" (Aufsicht von oben, dünn gepunktet) vs. "unter" (Untersicht von unten,
|
||||||
|
// gestrichelt wie die übrigen Überkopf-Projektionen).
|
||||||
|
if (detail !== "grob" && wt?.sillLine) {
|
||||||
|
const { faces, view } = wt.sillLine;
|
||||||
|
const dash = view === "unter" ? OVERHEAD_DASH : [0.04, 0.08];
|
||||||
|
if (faces === "aussen" || faces === "beide") {
|
||||||
|
out.push({ kind: "line", a: sym.frame[0], b: sym.frame[1], cls: "window-sill", weightMm: SYMBOL_HAIRLINE_MM, dash, color: strokeColor, greyed, openingId: o.id });
|
||||||
|
}
|
||||||
|
if (faces === "innen" || faces === "beide") {
|
||||||
|
out.push({ kind: "line", a: sym.frame[3], b: sym.frame[2], cls: "window-sill", weightMm: SYMBOL_HAIRLINE_MM, dash, color: strokeColor, greyed, openingId: o.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
// Rollladen-/Sonnenschutzkasten (P0-Minimalfassung, reine 2D-Kontur): ein
|
// Rollladen-/Sonnenschutzkasten (P0-Minimalfassung, reine 2D-Kontur): ein
|
||||||
// gestricheltes Rechteck ÜBER der Öffnung, ausserhalb der Wand-Aussenfläche —
|
// gestricheltes Rechteck ÜBER der Öffnung, ausserhalb der Wand-Aussenfläche —
|
||||||
// Breite = `shading.box.width` (0/fehlt ⇒ Öffnungsbreite), die zweite
|
// Breite = `shading.box.width` (0/fehlt ⇒ Öffnungsbreite), die zweite
|
||||||
|
|||||||
@@ -287,6 +287,43 @@ describe("generatePlan — Fensterbank (window-sill) folgt der eingezogenen Rahm
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("generatePlan — Auf-/Untersicht-Andeutung (WindowType.sillLine)", () => {
|
||||||
|
// "mittel" isoliert die sillLine-Linien sauber von der (fein-only) Fenster-
|
||||||
|
// bank-Box (dieselbe cls, andere Bedingung) — bei mittel kann Letztere nicht
|
||||||
|
// feuern, jede window-sill-Linie hier stammt also von sillLine.
|
||||||
|
it("fehlt sillLine -> keine window-sill-Linie", () => {
|
||||||
|
const p = project({ ...baseWindow, typeId: "wt1" }, [baseWindowType]);
|
||||||
|
expect(linesOfClass(p, "window-sill", "mittel").length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("faces: 'aussen' -> genau EINE Linie, an der Rahmen-Aussenkante", () => {
|
||||||
|
const wt: WindowType = { ...baseWindowType, sillLine: { faces: "aussen", view: "auf" } };
|
||||||
|
const p = project({ ...baseWindow, typeId: "wt1" }, [wt]);
|
||||||
|
const lines = linesOfClass(p, "window-sill", "mittel");
|
||||||
|
expect(lines.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("faces: 'beide' -> ZWEI Linien (aussen + innen)", () => {
|
||||||
|
const wt: WindowType = { ...baseWindowType, sillLine: { faces: "beide", view: "auf" } };
|
||||||
|
const p = project({ ...baseWindow, typeId: "wt1" }, [wt]);
|
||||||
|
expect(linesOfClass(p, "window-sill", "mittel").length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("bei 'grob' nie sichtbar, unabhängig von faces", () => {
|
||||||
|
const wt: WindowType = { ...baseWindowType, sillLine: { faces: "beide", view: "auf" } };
|
||||||
|
const p = project({ ...baseWindow, typeId: "wt1" }, [wt]);
|
||||||
|
expect(linesOfClass(p, "window-sill", "grob").length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("view: 'unter' nutzt die Überkopf-Strichelung (gestrichelt wie sonstige Überkopf-Projektionen)", () => {
|
||||||
|
const wt: WindowType = { ...baseWindowType, sillLine: { faces: "aussen", view: "unter" } };
|
||||||
|
const p = project({ ...baseWindow, typeId: "wt1" }, [wt]);
|
||||||
|
const lines = linesOfClass(p, "window-sill", "mittel");
|
||||||
|
expect(lines.length).toBe(1);
|
||||||
|
expect(lines[0].kind === "line" && lines[0].dash).toEqual([0.4, 0.25]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
|||||||
@@ -348,6 +348,35 @@ function BasisPanel({
|
|||||||
min={0}
|
min={0}
|
||||||
onCommit={(n) => patchWt({ insetFromFace: n })}
|
onCommit={(n) => patchWt({ insetFromFace: n })}
|
||||||
/>
|
/>
|
||||||
|
<SelField
|
||||||
|
label={t("oed.f.sillLineFaces")}
|
||||||
|
value={wt.sillLine?.faces ?? "off"}
|
||||||
|
options={[
|
||||||
|
{ value: "off", label: t("oed.sillLine.off") },
|
||||||
|
{ value: "aussen", label: t("oed.insetFace.aussen") },
|
||||||
|
{ value: "innen", label: t("oed.insetFace.innen") },
|
||||||
|
{ value: "beide", label: t("oed.sillLine.beide") },
|
||||||
|
]}
|
||||||
|
onChange={(v) =>
|
||||||
|
patchWt({
|
||||||
|
sillLine:
|
||||||
|
v === "off"
|
||||||
|
? undefined
|
||||||
|
: { faces: v as "aussen" | "innen" | "beide", view: wt.sillLine?.view ?? "auf" },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{wt.sillLine && (
|
||||||
|
<SelField
|
||||||
|
label={t("oed.f.sillLineView")}
|
||||||
|
value={wt.sillLine.view}
|
||||||
|
options={[
|
||||||
|
{ value: "auf", label: t("oed.sillLine.auf") },
|
||||||
|
{ value: "unter", label: t("oed.sillLine.unter") },
|
||||||
|
]}
|
||||||
|
onChange={(v) => patchWt({ sillLine: { faces: wt.sillLine!.faces, view: v } })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user