Dach: getrennter Überstand Traufe/Ortgang (statt ringsum)
Bisher ein einziger overhang ringsum (Designdoc-Prio #2). Neu Roof.overhangGable für den Ortgang (Giebelseite, entlang First); overhang gilt für die Traufe (senkrecht zum First). Fehlt overhangGable, gilt ringsum overhang (rückwärts- kompatibel). Geometrie mappt die Überstände je nach ridgeAxis auf die Outline- Achsen. Panel: zweites Feld 'Überstand Ortgang' (ausser flach/zelt). +3 Tests. 672/672 grün.
This commit is contained in:
@@ -154,4 +154,30 @@ describe("roofGeometry — Überstand", () => {
|
|||||||
const bb = roofBBox(g.eaves);
|
const bb = roofBBox(g.eaves);
|
||||||
expect(bb).toEqual({ x0: -0.5, y0: -0.5, x1: 6.5, y1: 4.5 });
|
expect(bb).toEqual({ x0: -0.5, y0: -0.5, x1: 6.5, y1: 4.5 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("getrennter Traufe/Ortgang-Überstand: First entlang X -> Ortgang weitet X, Traufe Y", () => {
|
||||||
|
// ridgeAxis "x": First entlang X -> Ortgang (Giebel) an den X-Enden,
|
||||||
|
// Traufe an den Y-Seiten. overhang(Traufe)=0.3, overhangGable(Ortgang)=0.8.
|
||||||
|
const g = roofGeometry(
|
||||||
|
roof({ shape: "sattel", ridgeAxis: "x", overhang: 0.3, overhangGable: 0.8 }),
|
||||||
|
E,
|
||||||
|
);
|
||||||
|
const bb = roofBBox(g.eaves);
|
||||||
|
expect(bb).toEqual({ x0: -0.8, y0: -0.3, x1: 6.8, y1: 4.3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getrennter Überstand: First entlang Y -> Ortgang weitet Y, Traufe X", () => {
|
||||||
|
const g = roofGeometry(
|
||||||
|
roof({ shape: "sattel", ridgeAxis: "y", overhang: 0.3, overhangGable: 0.8 }),
|
||||||
|
E,
|
||||||
|
);
|
||||||
|
const bb = roofBBox(g.eaves);
|
||||||
|
expect(bb).toEqual({ x0: -0.3, y0: -0.8, x1: 6.3, y1: 4.8 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overhangGable fehlt -> ringsum gleich overhang (rückwärtskompatibel)", () => {
|
||||||
|
const g = roofGeometry(roof({ shape: "sattel", overhang: 0.4 }), E);
|
||||||
|
const bb = roofBBox(g.eaves);
|
||||||
|
expect(bb).toEqual({ x0: -0.4, y0: -0.4, x1: 6.4, y1: 4.4 });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-5
@@ -336,11 +336,16 @@ const swapLine = (l: [Vec2, Vec2]): [Vec2, Vec2] => [swap2(l[0]), swap2(l[1])];
|
|||||||
*/
|
*/
|
||||||
export function roofGeometry(roof: Roof, eavesZ: number): RoofGeometry {
|
export function roofGeometry(roof: Roof, eavesZ: number): RoofGeometry {
|
||||||
const bb = roofBBox(roof.outline);
|
const bb = roofBBox(roof.outline);
|
||||||
const o = Math.max(0, roof.overhang);
|
const oe = Math.max(0, roof.overhang); // Traufüberstand (senkrecht zum First)
|
||||||
let x0 = bb.x0 - o;
|
const og = Math.max(0, roof.overhangGable ?? roof.overhang); // Ortgang (entlang First)
|
||||||
let y0 = bb.y0 - o;
|
// Der First liegt entlang der `ridgeAxis`-Achse: dort wirkt der Ortgang-, quer
|
||||||
let x1 = bb.x1 + o;
|
// dazu der Traufüberstand. In OUTLINE-Koordinaten je nach Firstrichtung mappen.
|
||||||
let y1 = bb.y1 + o;
|
const ox = roof.ridgeAxis === "x" ? og : oe;
|
||||||
|
const oy = roof.ridgeAxis === "x" ? oe : og;
|
||||||
|
let x0 = bb.x0 - ox;
|
||||||
|
let y0 = bb.y0 - oy;
|
||||||
|
let x1 = bb.x1 + ox;
|
||||||
|
let y1 = bb.y1 + oy;
|
||||||
const flip = roof.ridgeAxis === "y";
|
const flip = roof.ridgeAxis === "y";
|
||||||
if (flip) {
|
if (flip) {
|
||||||
[x0, y0, x1, y1] = [y0, x0, y1, x1];
|
[x0, y0, x1, y1] = [y0, x0, y1, x1];
|
||||||
|
|||||||
+2
-1
@@ -309,7 +309,8 @@ export const de = {
|
|||||||
"objinfo.roof.mansardKnee": "Knicklage (% Spannweite)",
|
"objinfo.roof.mansardKnee": "Knicklage (% Spannweite)",
|
||||||
"objinfo.roof.width": "Breite (m)",
|
"objinfo.roof.width": "Breite (m)",
|
||||||
"objinfo.roof.depth": "Tiefe (m)",
|
"objinfo.roof.depth": "Tiefe (m)",
|
||||||
"objinfo.roof.overhang": "Überstand (m)",
|
"objinfo.roof.overhang": "Überstand Traufe (m)",
|
||||||
|
"objinfo.roof.overhangGable": "Überstand Ortgang (m)",
|
||||||
"objinfo.roof.thickness": "Dachdicke (m)",
|
"objinfo.roof.thickness": "Dachdicke (m)",
|
||||||
"objinfo.roof.baseElevation": "Traufhöhe (m)",
|
"objinfo.roof.baseElevation": "Traufhöhe (m)",
|
||||||
"objinfo.roof.ridgeHeight": "Firsthöhe",
|
"objinfo.roof.ridgeHeight": "Firsthöhe",
|
||||||
|
|||||||
+2
-1
@@ -308,7 +308,8 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"objinfo.roof.mansardKnee": "Knee position (% span)",
|
"objinfo.roof.mansardKnee": "Knee position (% span)",
|
||||||
"objinfo.roof.width": "Width (m)",
|
"objinfo.roof.width": "Width (m)",
|
||||||
"objinfo.roof.depth": "Depth (m)",
|
"objinfo.roof.depth": "Depth (m)",
|
||||||
"objinfo.roof.overhang": "Overhang (m)",
|
"objinfo.roof.overhang": "Eaves overhang (m)",
|
||||||
|
"objinfo.roof.overhangGable": "Rake overhang (m)",
|
||||||
"objinfo.roof.thickness": "Roof thickness (m)",
|
"objinfo.roof.thickness": "Roof thickness (m)",
|
||||||
"objinfo.roof.baseElevation": "Eaves height (m)",
|
"objinfo.roof.baseElevation": "Eaves height (m)",
|
||||||
"objinfo.roof.ridgeHeight": "Ridge height",
|
"objinfo.roof.ridgeHeight": "Ridge height",
|
||||||
|
|||||||
+6
-1
@@ -1062,8 +1062,13 @@ export interface Roof {
|
|||||||
* gemessen von der Traufe. Kleiner ⇒ steiler Sockel kürzer; fehlt ⇒ 0.4.
|
* gemessen von der Traufe. Kleiner ⇒ steiler Sockel kürzer; fehlt ⇒ 0.4.
|
||||||
*/
|
*/
|
||||||
mansardKneeRatio?: number;
|
mansardKneeRatio?: number;
|
||||||
/** Dachüberstand an der Traufe (Meter, ringsum); 0 = bündig mit dem Umriss. */
|
/** Dachüberstand an der TRAUFE (Meter, senkrecht zum First); 0 = bündig. */
|
||||||
overhang: number;
|
overhang: number;
|
||||||
|
/**
|
||||||
|
* Dachüberstand am ORTGANG (Giebelseite, entlang des Firsts, Meter). Fehlt ⇒
|
||||||
|
* gleich dem Traufüberstand {@link overhang} (rückwärtskompatibel = ringsum).
|
||||||
|
*/
|
||||||
|
overhangGable?: number;
|
||||||
/** Firstrichtung (Sattel/Walm/Mansarde/Pult): entlang der X- oder Y-Achse. */
|
/** Firstrichtung (Sattel/Walm/Mansarde/Pult): entlang der X- oder Y-Achse. */
|
||||||
ridgeAxis: "x" | "y";
|
ridgeAxis: "x" | "y";
|
||||||
/** Traufhöhe (absolutes Z, Meter); fehlt ⇒ Oberkante des Geschosses. */
|
/** Traufhöhe (absolutes Z, Meter); fehlt ⇒ Oberkante des Geschosses. */
|
||||||
|
|||||||
@@ -577,10 +577,18 @@ export function RoofSection({
|
|||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Überstand + Dicke (Meter). */}
|
{/* Überstand: Traufe (senkrecht zum First) + Ortgang (Giebelseite). */}
|
||||||
{numField("objinfo.roof.overhang", roof.overhang, 0.05, (v) =>
|
{numField("objinfo.roof.overhang", roof.overhang, 0.05, (v) =>
|
||||||
host.onSetRoofPatch({ overhang: Math.max(0, v) }),
|
host.onSetRoofPatch({ overhang: Math.max(0, v) }),
|
||||||
)}
|
)}
|
||||||
|
{roof.shape !== "flach" &&
|
||||||
|
roof.shape !== "zelt" &&
|
||||||
|
numField(
|
||||||
|
"objinfo.roof.overhangGable",
|
||||||
|
roof.overhangGable ?? roof.overhang,
|
||||||
|
0.05,
|
||||||
|
(v) => host.onSetRoofPatch({ overhangGable: Math.max(0, v) }),
|
||||||
|
)}
|
||||||
{numField("objinfo.roof.thickness", roof.thickness, 0.02, (v) =>
|
{numField("objinfo.roof.thickness", roof.thickness, 0.02, (v) =>
|
||||||
host.onSetRoofPatch({ thickness: Math.max(0.01, v) }),
|
host.onSetRoofPatch({ thickness: Math.max(0.01, v) }),
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ export interface RoofInfo {
|
|||||||
mansardKneeRatio?: number;
|
mansardKneeRatio?: number;
|
||||||
/** Dachüberstand an der Traufe (Meter). */
|
/** Dachüberstand an der Traufe (Meter). */
|
||||||
overhang: number;
|
overhang: number;
|
||||||
|
/** Dachüberstand am Ortgang (Giebelseite, Meter); fehlt ⇒ = Traufüberstand. */
|
||||||
|
overhangGable?: number;
|
||||||
/** Firstrichtung entlang X oder Y. */
|
/** Firstrichtung entlang X oder Y. */
|
||||||
ridgeAxis: "x" | "y";
|
ridgeAxis: "x" | "y";
|
||||||
/** Dachdicke (Meter). */
|
/** Dachdicke (Meter). */
|
||||||
@@ -636,6 +638,7 @@ function roofSelection(project: Project, roof: Roof): Selection {
|
|||||||
mansardType: roof.mansardType,
|
mansardType: roof.mansardType,
|
||||||
mansardKneeRatio: roof.mansardKneeRatio,
|
mansardKneeRatio: roof.mansardKneeRatio,
|
||||||
overhang: roof.overhang,
|
overhang: roof.overhang,
|
||||||
|
overhangGable: roof.overhangGable,
|
||||||
ridgeAxis: roof.ridgeAxis,
|
ridgeAxis: roof.ridgeAxis,
|
||||||
thickness: roof.thickness,
|
thickness: roof.thickness,
|
||||||
baseElevation: eavesZ,
|
baseElevation: eavesZ,
|
||||||
|
|||||||
Reference in New Issue
Block a user