Schnitt/Ansicht Phase 1: Wand unter Schnittebene als Ansichts-Umriss (viewOnly, keine Poché), mit Tests
This commit is contained in:
@@ -677,7 +677,14 @@ export function generatePlan(
|
||||
const openings = openingsOfWall(project, wall.id);
|
||||
const gaps = wallGaps(wall, doors, openings);
|
||||
const cuts = joins.get(wall.id) ?? { startCut: null, endCut: null };
|
||||
addWallPoche(primitives, project, wall, gaps, cuts, wallGreyed, detail, wallLwMm, wallCategory);
|
||||
// Schnitt vs. Ansicht nach Schnitthöhe (BIM-Standard): erreicht die Wand die
|
||||
// Grundriss-Schnittebene nicht (Höhe < Schnitthöhe des Geschosses, z. B. eine
|
||||
// Brüstung/ein Podestrand), wird sie nur als Ansichts-Umriss gezeichnet —
|
||||
// keine Schnitt-Poché. Normale Wände (Höhe ≥ Schnitthöhe) sind unberührt.
|
||||
const wallViewOnly = wall.height < (floor?.cutHeight ?? 1.0);
|
||||
addWallPoche(
|
||||
primitives, project, wall, gaps, cuts, wallGreyed, detail, wallLwMm, wallCategory, wallViewOnly,
|
||||
);
|
||||
// Referenzlinie: dünne gestrichelte Wand-Achslinie (von der Oberleiste
|
||||
// umschaltbar). Wird über der Poché gezeichnet, damit sie sichtbar bleibt.
|
||||
if (showReferenceLines) addReferenceLine(primitives, wall, wallGreyed);
|
||||
@@ -1258,6 +1265,10 @@ function addWallPoche(
|
||||
detail: DetailLevel,
|
||||
wallLwMm: number,
|
||||
category: LayerCategory | undefined,
|
||||
// Wand liegt GANZ unter der Grundriss-Schnittebene (z. B. 30-cm-Brüstung bei
|
||||
// 1 m Schnitthöhe) → „von oben gesehen": nur Ansichts-Umriss (Haarlinie), KEINE
|
||||
// Schnitt-Poché/Schraffur/Schichten. Der reguläre Schnittfall bleibt `false`.
|
||||
viewOnly: boolean,
|
||||
): void {
|
||||
const wt = getWallType(project, wall);
|
||||
const total = wallTypeThickness(wt);
|
||||
@@ -1326,6 +1337,24 @@ function addWallPoche(
|
||||
const startCut = s <= 1e-6 ? cuts.startCut : null;
|
||||
const endCut = e >= axisLen - 1e-6 ? cuts.endCut : null;
|
||||
|
||||
if (viewOnly) {
|
||||
// Ansichts-Wand (unter der Schnittebene): ein Umriss über die volle Dicke
|
||||
// als Haarlinie, ohne Füllung/Schraffur. Segment-/Gehrungs-/Öffnungs-Logik
|
||||
// bleibt dieselbe wie im Schnittfall (nur eben ohne Poché).
|
||||
out.push({
|
||||
kind: "polygon",
|
||||
pts: clippedBand(p1, p2, refOff - total / 2, refOff + total / 2, startCut, endCut),
|
||||
fill: "none",
|
||||
stroke,
|
||||
strokeWidthMm: layerLineMm,
|
||||
hatch: NO_HATCH,
|
||||
greyed,
|
||||
noStrokeEdges: joinEdges(startCut, endCut),
|
||||
wallId,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (detail === "grob") {
|
||||
// Eine Sammelfläche über die volle Dicke, ohne Schraffur, dicker Umriss.
|
||||
out.push({
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Unit-Tests für „Schnitt vs. Ansicht nach Schnitthöhe" (generatePlan):
|
||||
* Eine Wand, die die Grundriss-Schnittebene NICHT erreicht (Höhe < `cutHeight`,
|
||||
* z. B. eine Brüstung), wird nur als Ansichts-Umriss gezeichnet — Poché-Polygone
|
||||
* OHNE Füllung (`fill:"none"`), keine Schraffur. Eine normale Wand (Höhe ≥
|
||||
* Schnitthöhe) behält die geschnittene Poché (gefüllte Bänder).
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { generatePlan } from "./generatePlan";
|
||||
import type { Project, Wall } from "../model/types";
|
||||
|
||||
/** Minimalprojekt: eine normale Wand (2.6 m) + eine niedrige Wand (0.3 m). */
|
||||
function project(): Project {
|
||||
const wall = (id: string, start: Wall["start"], end: Wall["end"], height: number): Wall => ({
|
||||
id,
|
||||
type: "wall",
|
||||
floorId: "eg",
|
||||
categoryCode: "20",
|
||||
start,
|
||||
end,
|
||||
wallTypeId: "aw",
|
||||
height,
|
||||
});
|
||||
return {
|
||||
id: "t",
|
||||
name: "T",
|
||||
lineStyles: [{ id: "thin", name: "d", weight: 0.13, color: "#111", dash: null }],
|
||||
hatches: [{ id: "none", name: "Ohne", pattern: "none", scale: 1, angle: 0, color: "#111" }],
|
||||
components: [{ id: "a", name: "A", color: "#d8d2c7", hatchId: "none", joinPriority: 10 }],
|
||||
wallTypes: [{ id: "aw", name: "AW", layers: [{ componentId: "a", thickness: 0.2 }] }],
|
||||
drawingLevels: [
|
||||
{ id: "eg", name: "EG", kind: "floor", visible: true, locked: false, floorHeight: 2.6, cutHeight: 1.0, baseElevation: 0 },
|
||||
],
|
||||
layers: [{ code: "20", name: "Wände", color: "#0a0a0a", lw: 0.5, visible: true, locked: false }],
|
||||
walls: [
|
||||
wall("HI", { x: 0, y: 0 }, { x: 4, y: 0 }, 2.6), // erreicht Schnitt → Poché
|
||||
wall("LOW", { x: 0, y: 2 }, { x: 4, y: 2 }, 0.3), // Brüstung → nur Ansicht
|
||||
],
|
||||
doors: [],
|
||||
openings: [],
|
||||
ceilings: [],
|
||||
stairs: [],
|
||||
rooms: [],
|
||||
drawings2d: [],
|
||||
context: [],
|
||||
};
|
||||
}
|
||||
|
||||
const visible = new Set(["20"]);
|
||||
|
||||
describe("generatePlan — Schnitt vs. Ansicht nach Schnitthöhe", () => {
|
||||
it("zeichnet die niedrige Wand nur als Ansichts-Umriss (fill none), die normale mit Poché", () => {
|
||||
const plan = generatePlan(project(), "eg", visible, undefined, "mittel");
|
||||
const wallPolys = (id: string) =>
|
||||
plan.primitives.filter(
|
||||
(p): p is Extract<typeof p, { kind: "polygon" }> =>
|
||||
p.kind === "polygon" && p.wallId === id,
|
||||
);
|
||||
|
||||
const hi = wallPolys("HI");
|
||||
const low = wallPolys("LOW");
|
||||
expect(hi.length).toBeGreaterThan(0);
|
||||
expect(low.length).toBeGreaterThan(0);
|
||||
|
||||
// Normale Wand: mindestens ein gefülltes Poché-Band (Bauteil-Farbe).
|
||||
expect(hi.some((p) => p.fill !== "none")).toBe(true);
|
||||
// Niedrige Wand: ALLE Polygone ohne Füllung (reiner Ansichts-Umriss).
|
||||
expect(low.every((p) => p.fill === "none")).toBe(true);
|
||||
});
|
||||
|
||||
it("behandelt die niedrige Wand als geschnitten, wenn die Schnitthöhe darunter liegt", () => {
|
||||
const p = project();
|
||||
p.drawingLevels[0].cutHeight = 0.2; // Schnittebene unter 0.3 m → auch LOW geschnitten
|
||||
const plan = generatePlan(p, "eg", visible, undefined, "mittel");
|
||||
const low = plan.primitives.filter(
|
||||
(pr): pr is Extract<typeof pr, { kind: "polygon" }> =>
|
||||
pr.kind === "polygon" && pr.wallId === "LOW",
|
||||
);
|
||||
expect(low.some((pr) => pr.fill !== "none")).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user