Decken-Aussparungen: Treppenauge/Schacht in 2D und 3D (Brücken-Trick)
Ceiling.openings (e469a93) wird jetzt gerendert. Kern: mergeHoles() fügt
Löcher über schmale Brücken (1e-4) in den Aussenring ein → EIN einfacher
Polygonring, paritätskorrekt für SVG-Fill, Scanline-Hatch, Ear-Clipping
(render3d) und Punkt-in-Polygon-Pick — kein Loch-Support in den Primitiven
nötig. Loch-Orientierung gegenläufig (signedArea), Sichtbarkeitstest für die
Brücke, mehrere Löcher iterativ.
- 2D (addCeilingPoche): Poché-Ring mit Löchern; Brückenkanten via
noStrokeEdges unsichtbar, Lochränder werden als Ringkanten gestrichen
(wand-footprint-geclippt wie der Aussenumriss)
- 3D (emitSlabs): derselbe Ring für alle Schichten; Klick ins Loch trifft
die Decke nicht (Pick paritätskorrekt)
- Ohne openings byte-identischer Codepfad (Regressionstests)
+11 Tests (Ring-Einfachheit, Shoelace-Flächen, 2-Loch-Fall, Regressionen).
684/684 grün. 3D visuell in Tauri zu prüfen.
This commit is contained in:
@@ -45,6 +45,7 @@ import {
|
||||
wallTypeThickness,
|
||||
} from "../model/types";
|
||||
import { columnFootprint } from "../geometry/column";
|
||||
import { mergeHoles } from "../geometry/polygonHoles";
|
||||
import { roofGeometry } from "../geometry/roof";
|
||||
import { effectiveOverrides, hasOverrides } from "../overrides/engine";
|
||||
import { evaluateRoom, siaLabel } from "../geometry/roomArea";
|
||||
@@ -2466,6 +2467,13 @@ function addCeilingPoche(
|
||||
): void {
|
||||
const pts = ceiling.outline;
|
||||
if (pts.length < 3) return;
|
||||
// Aussparungen (Treppenauge/Schacht) über den Brücken-Trick in EINEN
|
||||
// einfachen Poché-Ring einfügen: kein `openings` ⇒ `ring === pts` und
|
||||
// `bridgeEdges` leer (exakt heutiger Codepfad, siehe {@link mergeHoles}).
|
||||
const ceilingHoles = ceiling.openings ?? [];
|
||||
const { ring, bridgeEdges } = ceilingHoles.length
|
||||
? mergeHoles(pts, ceilingHoles)
|
||||
: { ring: pts, bridgeEdges: [] as number[] };
|
||||
const wt = getCeilingType(project, ceiling);
|
||||
const comp = wt.layers.length > 0 ? getComponent(project, wt.layers[0].componentId) : null;
|
||||
const stroke = ceiling.color ?? POCHE_STROKE;
|
||||
@@ -2500,23 +2508,29 @@ function addCeilingPoche(
|
||||
// Trägt weiterhin die `ceilingId` für die Links-Klick-Auswahl der Fläche.
|
||||
out.push({
|
||||
kind: "polygon",
|
||||
pts,
|
||||
pts: ring,
|
||||
fill: comp ? pocheFill(hatch.pattern) : "none",
|
||||
stroke: "none",
|
||||
strokeWidthMm: 0,
|
||||
hatch,
|
||||
greyed,
|
||||
ceilingId,
|
||||
...(bridgeEdges.length ? { noStrokeEdges: bridgeEdges } : {}),
|
||||
});
|
||||
// Kräftige Umrisslinie NUR dort, wo keine Wand die Decke verdeckt: jede
|
||||
// Umriss-Kante gegen die Wand-Footprints clippen und die freien Teilstücke als
|
||||
// Linien ausgeben. Liegt die Decke deckungsgleich unter den Wänden (Regelfall:
|
||||
// Deckenumriss = Gebäude-/Wand-Mittellinien), entfällt der Umriss ganz; nur
|
||||
// Überstände (Vordach/Balkon jenseits der Wand) bleiben sichtbar.
|
||||
const n = pts.length;
|
||||
// Umriss-Kante (inkl. Lochränder, die als Teil des Rings automatisch
|
||||
// mitgestrichen werden) gegen die Wand-Footprints clippen und die freien
|
||||
// Teilstücke als Linien ausgeben. Liegt die Decke deckungsgleich unter den
|
||||
// Wänden (Regelfall: Deckenumriss = Gebäude-/Wand-Mittellinien), entfällt
|
||||
// der Umriss ganz; nur Überstände (Vordach/Balkon jenseits der Wand)
|
||||
// bleiben sichtbar. Brückenkanten (künstliche Verbindung Aussenring↔Loch)
|
||||
// werden NICHT gestrichen.
|
||||
const n = ring.length;
|
||||
const bridgeSet = new Set(bridgeEdges);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const a = pts[i];
|
||||
const b = pts[(i + 1) % n];
|
||||
if (bridgeSet.has(i)) continue;
|
||||
const a = ring[i];
|
||||
const b = ring[(i + 1) % n];
|
||||
const holes: Array<[number, number]> = [];
|
||||
for (const fp of wallFootprints) {
|
||||
const iv = segmentInsideWallBand(a, b, fp);
|
||||
|
||||
Reference in New Issue
Block a user