Dach: Auswahl-Hervorhebung auch im 3D (Draht-Umriss der Dachflächen)

selectionHighlightLines bekommt roofIds und emittiert die Kanten aller
Dachflächen + Giebel (roofGeometry, Modell→World) als Akzent-Draht — ein
selektiertes Dach leuchtet jetzt im wgpu-Viewport wie Wand/Decke. App reicht
selectedRoofIds durch. +1 Test. tsc + vitest grün.
This commit is contained in:
2026-07-10 00:35:26 +02:00
parent eaf57e2292
commit 64f61797d8
3 changed files with 35 additions and 2 deletions
+4 -1
View File
@@ -5984,7 +5984,8 @@ function Content({
selectedWallIds.length === 0 &&
selectedCeilingIds.length === 0 &&
selectedOpeningIds.length === 0 &&
selectedStairIds.length === 0
selectedStairIds.length === 0 &&
selectedRoofIds.length === 0
? null
: selectionHighlightLines(
project,
@@ -5993,6 +5994,7 @@ function Content({
HIGHLIGHT_RGB,
selectedOpeningIds,
selectedStairIds,
selectedRoofIds,
),
[
project,
@@ -6000,6 +6002,7 @@ function Content({
selectedCeilingIds,
selectedOpeningIds,
selectedStairIds,
selectedRoofIds,
],
);
+9
View File
@@ -27,6 +27,15 @@ describe("selectionHighlightLines", () => {
expect(lines.length).toBe(0);
});
it("ein Dach -> Draht-Kanten der Dachflächen/Giebel (nicht leer, mit Farbe)", () => {
// sampleProject enthält das Demo-Satteldach RF1.
const lines = selectionHighlightLines(sampleProject, [], [], RGB, [], [], ["RF1"]);
expect(lines.length).toBeGreaterThan(0);
// Farbe in jedem Vertex (erstes Vertex: [px,py,pz,r,g,b]).
expect(lines[3]).toBeCloseTo(RGB[0], 6);
expect(lines[5]).toBeCloseTo(RGB[2], 6);
});
it("eine Wand ohne Öffnungen -> je Materiallage ein Quader, Kern-Band unter der Decke gespalten (5·12 Kanten)", () => {
// W2 (EG, Ost) trägt keine Tür/kein Fenster im Sample-Projekt (nur W1/W3
// haben Öffnungen, s. sampleProject.ts) -> ein Achsen-Teilquader. Wandtyp
+22 -1
View File
@@ -2254,12 +2254,14 @@ export function selectionHighlightLines(
rgb: RRgb,
openingIds: string[] = [],
stairIds: string[] = [],
roofIds: string[] = [],
): Float32Array {
if (
wallIds.length === 0 &&
ceilingIds.length === 0 &&
openingIds.length === 0 &&
stairIds.length === 0
stairIds.length === 0 &&
roofIds.length === 0
) {
return new Float32Array(0);
}
@@ -2268,6 +2270,7 @@ export function selectionHighlightLines(
const ceilingSet = new Set(ceilingIds);
const openingSet = new Set(openingIds);
const stairSet = new Set(stairIds);
const roofSet = new Set(roofIds);
const out: number[] = [];
const pushEdge = (a: RVec3, b: RVec3) => {
out.push(a[0], a[1], a[2], rgb[0], rgb[1], rgb[2]);
@@ -2332,5 +2335,23 @@ export function selectionHighlightLines(
for (const o of openings) if (openingSet.has(o.openingId)) boxEdges(o);
for (const s of slabs) if (ceilingSet.has(s.ceilingId)) prismEdges(s.outline, s.zBottom, s.zTop);
for (const st of stairs) if (stairSet.has(st.stairId)) prismEdges(st.outline, st.zBottom, st.zTop);
// Dach: die Kanten aller Dachflächen + Giebel als Draht-Umriss (roofGeometry
// liefert 3D-Polygone in MODELL-Koordinaten [x, y, z=Höhe]; world = [x, z, y]).
if (roofSet.size > 0) {
for (const roof of project.roofs ?? []) {
if (!roofSet.has(roof.id)) continue;
const g = roofGeometry(roof, roofBaseElevation(project, roof));
const polyEdges = (pts: readonly (readonly [number, number, number])[]) => {
const m = pts.length;
for (let i = 0, j = m - 1; i < m; j = i++) {
const a = pts[j];
const b = pts[i];
pushEdge([a[0], a[2], a[1]], [b[0], b[2], b[1]]);
}
};
for (const pl of g.planes) polyEdges(pl.pts);
for (const gab of g.gables) polyEdges(gab);
}
}
return new Float32Array(out);
}