Tür: zweiflügelige Türen wirklich rendern (leafCount 2D+3D)
DoorType.leafCount wurde bisher von keinem Renderer gelesen — eine zweiflügelige Tür sah aus wie eine einflügelige (Designdoc-Prio #3). - opening.ts: additive doorLeaves(wall, o, leafCount) — einflüglig ein Blatt voller Breite, zweiflüglig zwei halbbreite Blätter an gegenüberliegenden Pfosten, beide zur selben Seite (symmetrische Doppeltür). doorSymbol bleibt unverändert (Parität). - generatePlan: Tür-Zweig zeichnet je Blatt Linie + Schwenkbogen (leafCount aus DoorType). - toWalls3d: Tür-wingCount = leafCount → mittiger Anschlagpfosten (Stulp) im 3D. - +1 Test (2 Blätter/Bögen, halber Radius). 669/669 grün.
This commit is contained in:
@@ -162,6 +162,63 @@ export function doorSymbol(wall: Wall, o: Opening): DoorSymbol | null {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ein einzelnes Türblatt (Grundriss): Scharnier + geschlossenes/offenes Ende + Radius. */
|
||||||
|
export interface DoorLeaf {
|
||||||
|
hinge: Vec2;
|
||||||
|
openEnd: Vec2;
|
||||||
|
closedEnd: Vec2;
|
||||||
|
radius: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Blatt-Geometrie aus Scharnier, geschlossener Richtung, Schwenkrichtung, Winkel, Breite. */
|
||||||
|
function computeLeaf(
|
||||||
|
hinge: Vec2,
|
||||||
|
closedDir: Vec2,
|
||||||
|
swingDir: Vec2,
|
||||||
|
angle: number,
|
||||||
|
width: number,
|
||||||
|
): DoorLeaf {
|
||||||
|
const openDir = {
|
||||||
|
x: closedDir.x * Math.cos(angle) + swingDir.x * Math.sin(angle),
|
||||||
|
y: closedDir.y * Math.cos(angle) + swingDir.y * Math.sin(angle),
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
hinge,
|
||||||
|
openEnd: add(hinge, scale(openDir, width)),
|
||||||
|
closedEnd: add(hinge, scale(closedDir, width)),
|
||||||
|
radius: width,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Türblätter im Grundriss (1 oder 2 Flügel). Einflüglig: ein Blatt über die
|
||||||
|
* volle Breite (Scharnier je `o.hinge`). Zweiflüglig (`leafCount === 2`): zwei
|
||||||
|
* halbbreite Blätter, an den GEGENüberliegenden Pfosten angeschlagen, beide zur
|
||||||
|
* selben Seite schwenkend (symmetrische Doppeltür / Stulptür). `doorSymbol`
|
||||||
|
* bleibt unverändert (einflügelig) für Bestandspfade/Parität; dies ist additiv.
|
||||||
|
*/
|
||||||
|
export function doorLeaves(wall: Wall, o: Opening, leafCount: number): DoorLeaf[] {
|
||||||
|
const jambs = openingJambs(wall, o);
|
||||||
|
if (!jambs) return [];
|
||||||
|
const { u, n } = wallAxisFrame(wall);
|
||||||
|
const { jambStart, jambEnd } = jambs;
|
||||||
|
const width = Math.hypot(jambEnd.x - jambStart.x, jambEnd.y - jambStart.y);
|
||||||
|
const swingSign = (o.swing ?? "left") === "left" ? 1 : -1;
|
||||||
|
const dirSign = (o.openingDir ?? "in") === "in" ? 1 : -1;
|
||||||
|
const swingDir = scale(n, swingSign * dirSign);
|
||||||
|
const angle = (o.swingAngle ?? 90) * DEG;
|
||||||
|
if (leafCount >= 2) {
|
||||||
|
const hw = width / 2;
|
||||||
|
return [
|
||||||
|
computeLeaf(jambStart, u, swingDir, angle, hw),
|
||||||
|
computeLeaf(jambEnd, scale(u, -1), swingDir, angle, hw),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
const hinge = (o.hinge ?? "start") === "start" ? jambStart : jambEnd;
|
||||||
|
const closedDir = (o.hinge ?? "start") === "start" ? u : scale(u, -1);
|
||||||
|
return [computeLeaf(hinge, closedDir, swingDir, angle, width)];
|
||||||
|
}
|
||||||
|
|
||||||
// ── Fenster-Symbol (Plan) ────────────────────────────────────────────────────
|
// ── Fenster-Symbol (Plan) ────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Aufgelöste Fenster-Linien im Grundriss (Weltkoordinaten). */
|
/** Aufgelöste Fenster-Linien im Grundriss (Weltkoordinaten). */
|
||||||
|
|||||||
@@ -164,6 +164,30 @@ describe("generatePlan — Tür-/Fenstertyp-Rahmenband (opening-frame)", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("generatePlan — zweiflügelige Tür (leafCount)", () => {
|
||||||
|
it("leafCount 2 -> zwei Türblätter + zwei Schwenkbögen; leafCount 1 -> je eines", () => {
|
||||||
|
const oneLeaf = project([{ ...baseDoor, typeId: "dt1" }], { doorTypes: [doorType] });
|
||||||
|
expect(linesOfClass(oneLeaf, "door-leaf", "T1").length).toBe(1);
|
||||||
|
|
||||||
|
const twoLeafType: DoorType = { ...doorType, id: "dt2", leafCount: 2 };
|
||||||
|
const twoLeaf = project([{ ...baseDoor, typeId: "dt2" }], { doorTypes: [twoLeafType] });
|
||||||
|
expect(linesOfClass(twoLeaf, "door-leaf", "T1").length).toBe(2);
|
||||||
|
// Zwei Schwenkbögen (arc-Primitive) bei mittel/fein.
|
||||||
|
const arcs = generatePlan(twoLeaf, "eg", visible, undefined, "mittel").primitives.filter(
|
||||||
|
(pr) => pr.kind === "arc" && (pr as { cls?: string }).cls === "door-swing",
|
||||||
|
);
|
||||||
|
expect(arcs.length).toBe(2);
|
||||||
|
// Die beiden Blätter sind je halb so breit (Radius) wie das einflügelige.
|
||||||
|
const oneRadius = (
|
||||||
|
generatePlan(oneLeaf, "eg", visible, undefined, "mittel").primitives.find(
|
||||||
|
(pr) => pr.kind === "arc" && (pr as { cls?: string }).cls === "door-swing",
|
||||||
|
) as { r: number }
|
||||||
|
).r;
|
||||||
|
const twoRadius = (arcs[0] as { r: number }).r;
|
||||||
|
expect(twoRadius).toBeCloseTo(oneRadius / 2, 6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("generatePlan — Oberlicht (opening-transom)", () => {
|
describe("generatePlan — Oberlicht (opening-transom)", () => {
|
||||||
it("erscheint genau dann, wenn transomHeight > 0", () => {
|
it("erscheint genau dann, wenn transomHeight > 0", () => {
|
||||||
const withTransom = project([{ ...baseWindow, typeId: "wt1" }], { windowTypes: [windowType] });
|
const withTransom = project([{ ...baseWindow, typeId: "wt1" }], { windowTypes: [windowType] });
|
||||||
|
|||||||
+23
-18
@@ -57,6 +57,7 @@ import { stairGeometry, stairCut, stairOutline } from "../geometry/stair";
|
|||||||
import type { SpiralOutlineSegments } from "../geometry/stair";
|
import type { SpiralOutlineSegments } from "../geometry/stair";
|
||||||
import {
|
import {
|
||||||
doorSymbol,
|
doorSymbol,
|
||||||
|
doorLeaves,
|
||||||
openingGapQuad,
|
openingGapQuad,
|
||||||
openingInterval,
|
openingInterval,
|
||||||
openingJambs,
|
openingJambs,
|
||||||
@@ -2115,29 +2116,33 @@ function addOpeningSymbol(
|
|||||||
// Entspricht `oeff_tuer_typ == "wandoeffnung"` im Rhino-Plugin.
|
// Entspricht `oeff_tuer_typ == "wandoeffnung"` im Rhino-Plugin.
|
||||||
const isWandoeffnung = (o.doorType ?? "normal") === "wandoeffnung";
|
const isWandoeffnung = (o.doorType ?? "normal") === "wandoeffnung";
|
||||||
if (!isWandoeffnung) {
|
if (!isWandoeffnung) {
|
||||||
// Türblatt (alle Stufen).
|
// Türblätter (1 oder 2 Flügel, je DoorType.leafCount). Zweiflüglig ⇒ zwei
|
||||||
out.push({
|
// halbbreite Blätter an gegenüberliegenden Pfosten (symmetrische Doppeltür).
|
||||||
kind: "line",
|
const leafCount = Math.max(1, Math.round(getDoorType(project, o)?.leafCount ?? 1));
|
||||||
a: sym.hinge,
|
for (const leaf of doorLeaves(wall, o, leafCount)) {
|
||||||
b: sym.openEnd,
|
|
||||||
cls: "door-leaf",
|
|
||||||
weightMm: SYMBOL_HAIRLINE_MM,
|
|
||||||
greyed,
|
|
||||||
openingId: o.id,
|
|
||||||
});
|
|
||||||
// mittel + fein: Schwenkbogen von geschlossen → offen.
|
|
||||||
if (detail !== "grob") {
|
|
||||||
out.push({
|
out.push({
|
||||||
kind: "arc",
|
kind: "line",
|
||||||
center: sym.hinge,
|
a: leaf.hinge,
|
||||||
from: sym.closedEnd,
|
b: leaf.openEnd,
|
||||||
to: sym.openEnd,
|
cls: "door-leaf",
|
||||||
r: sym.radius,
|
|
||||||
cls: "door-swing",
|
|
||||||
weightMm: SYMBOL_HAIRLINE_MM,
|
weightMm: SYMBOL_HAIRLINE_MM,
|
||||||
greyed,
|
greyed,
|
||||||
openingId: o.id,
|
openingId: o.id,
|
||||||
});
|
});
|
||||||
|
// mittel + fein: Schwenkbogen von geschlossen → offen je Blatt.
|
||||||
|
if (detail !== "grob") {
|
||||||
|
out.push({
|
||||||
|
kind: "arc",
|
||||||
|
center: leaf.hinge,
|
||||||
|
from: leaf.closedEnd,
|
||||||
|
to: leaf.openEnd,
|
||||||
|
r: leaf.radius,
|
||||||
|
cls: "door-swing",
|
||||||
|
weightMm: SYMBOL_HAIRLINE_MM,
|
||||||
|
greyed,
|
||||||
|
openingId: o.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fein: Anschlag-Striche an beiden Pfosten (quer zur Wand).
|
// fein: Anschlag-Striche an beiden Pfosten (quer zur Wand).
|
||||||
|
|||||||
@@ -1970,7 +1970,8 @@ function resolveOpeningFrame(project: Project, wall: Wall, op: Opening): Opening
|
|||||||
nMax,
|
nMax,
|
||||||
transomHeight: Math.max(0, dt.transomHeight ?? 0),
|
transomHeight: Math.max(0, dt.transomHeight ?? 0),
|
||||||
hasSill: dt.threshold === true,
|
hasSill: dt.threshold === true,
|
||||||
wingCount: 1,
|
// Zweiflüglige Tür ⇒ 2 Flügel = ein mittiger Anschlagpfosten (Stulp) im 3D.
|
||||||
|
wingCount: Math.max(1, Math.round(dt.leafCount ?? 1)),
|
||||||
mullionRows: 1,
|
mullionRows: 1,
|
||||||
glazed: dt.leafStyle === "glas",
|
glazed: dt.leafStyle === "glas",
|
||||||
glazingPanes: 1,
|
glazingPanes: 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user