SIA-Wandschraffuren: Beton-Kreuz, Backstein/Dämmung wandrelativ, hairline

- HatchStyle.relativeToWall: der Wandwinkel (aus wall.start/end) wird in
  addWallPoche und resolveWallSectionStyle auf hatch.angle addiert (Vorzeichen
  zur SVG-CW-Konvention passend), sodass die Schraffur der Wandachse folgt.
- sampleProject: sia-concrete (crosshatch 45° absolut), sia-brick (diagonal
  wandrelativ), sia-insulation (diagonal 90° wandrelativ, solide Haarlinie);
  Komponenten Beton/Backstein/Dämmung darauf umgehängt. Dichte Muster (scale
  0.5/0.5/0.45), hairline (Strichgewicht 0.05 → 0.6px-Boden).
- Poché-Hinterlegung: mehrschichtige Wände (und einschichtige) weiss, solid
  schwarz (Fill + Schraffurfarbe), konsistent in SVG- und glPlan-Fill; Mono
  behält Schwarz für solid. Gilt auch für den Schnitt-Cut.
- ResourceManager: Wandbezug-Schalter im Hatch-Manager. exportDxf: Dash-
  Splitting für gestrichelte Schraffuren.
This commit is contained in:
2026-07-03 19:47:03 +02:00
parent 6aaef8f46a
commit e9dc423a12
7 changed files with 187 additions and 32 deletions
+44 -1
View File
@@ -126,8 +126,13 @@ function emitPrimitive(
p.hatch.pattern !== "none" &&
p.hatch.pattern !== "solid"
) {
const cycle = dashCycleMeters(p.hatch.dash);
for (const seg of hatchSegments(pts, p.hatch)) {
dxf.addLine(LAYER_HATCH, seg.a, seg.b);
if (cycle) {
for (const d of dashSeg(seg, cycle)) dxf.addLine(LAYER_HATCH, d.a, d.b);
} else {
dxf.addLine(LAYER_HATCH, seg.a, seg.b);
}
}
}
break;
@@ -174,6 +179,44 @@ interface Seg {
b: Vec2;
}
// Strichmuster (`dash`, mm-Papier) → Zyklus in WELT-Metern. Gleiche Umrechnung
// wie der Bildschirm/GL-Pfad: 1 mm ≙ (1/0.13) Kachel-Einheiten, 1 Einheit = 1/90 m.
// `null` = durchgezogen (keine Zerlegung). Ungerade Musterlängen werden verdoppelt.
const DASH_MM_TO_M = (1 / 0.13) * (1 / 90);
function dashCycleMeters(dash: number[] | null): number[] | null {
if (!dash || dash.length === 0) return null;
const pat = dash.map((d) => Math.max(0, d) * DASH_MM_TO_M).filter((d) => d > 0);
if (pat.length === 0) return null;
return pat.length % 2 === 0 ? pat : pat.concat(pat);
}
// Zerlegt EINE Schraffurlinie in ihre durchgezogenen Teilstücke gemäß `cycle`
// ([an, aus, …], Meter). Die Phase startet je Linie neu — visuell identisch zum
// SVG-`<pattern>`, das das Muster je Kachellinie zurücksetzt.
function dashSeg(seg: Seg, cycle: number[]): Seg[] {
const out: Seg[] = [];
const len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
if (len < 1e-9) return out;
const ux = (seg.b.x - seg.a.x) / len;
const uy = (seg.b.y - seg.a.y) / len;
let pos = 0;
let idx = 0;
let on = true;
while (pos < len - 1e-9) {
const step = Math.min(cycle[idx % cycle.length], len - pos);
if (on && step > 1e-9) {
out.push({
a: { x: seg.a.x + ux * pos, y: seg.a.y + uy * pos },
b: { x: seg.a.x + ux * (pos + step), y: seg.a.y + uy * (pos + step) },
});
}
pos += step;
idx++;
on = !on;
}
return out;
}
function hatchSegments(
poly: Vec2[],
hatch: Extract<Primitive, { kind: "polygon" }>["hatch"],