Schraffuren monochrom: Vordergrund #0f0f0f, Hintergrund #f0f0f0

Alle Schraffuren (Schnitt- UND Oberflaechen-/Ansichtsschraffur) rendern
jetzt einheitlich in Tinte #0f0f0f auf Papier #f0f0f0 statt in der
Materialfarbe des Bauteils (Backstein braun, Beton grau). HATCH_INK/
HATCH_PAPER ersetzen POCHE_FILL_BLACK/WHITE; resolveHatch liefert immer
HATCH_INK als Linienfarbe. Der generische Schnitt-Fallback nutzte bisher
die rohe 3D-Albedo (Ursache des Backstein-Brauns bei nicht aufloesbarer
Schicht) -> jetzt HATCH_PAPER. Print-Mono-Modus unberuehrt. 216 Tests gruen.
This commit is contained in:
2026-07-04 14:33:35 +02:00
parent da39f0c8d5
commit 5ed3549c66
2 changed files with 74 additions and 58 deletions
+64 -55
View File
@@ -291,19 +291,20 @@ export type Primitive =
* {@link HatchRender} auf. Der Linienstil der Musterlinien wird, falls gesetzt,
* aus dem Line Manager nachgeschlagen; sonst gilt ein dünner Default.
*
* FARB-RESOLVE-REIHENFOLGE (Vordergrund = Muster-/Schraffurlinienfarbe):
* `foreground` (Parameter: bereits aufgelöste Attribut-Override ?? Component.
* foreground) ?? `HatchStyle.color` (deprecated Backward-Compat) .
* Der Aufrufer reicht den bereits kollabierten Vordergrund als `foreground`
* durch (siehe {@link resolveForeground}); ist er `undefined` (Sample: weder
* Attribut- noch Bauteil-Vordergrund gesetzt), greift der HatchStyle.color-
* Fallback → pixelidentisch zum bisherigen Verhalten.
* VORDERGRUND (Muster-/Schraffurlinienfarbe bzw. Vollfüllung): AKTUELL fest auf
* {@link HATCH_INK} gezwungen (Nutzer-Vorgabe 2026-07-04) — Schnitt- WIE
* Oberflächen-/Ansichtsschraffur zeigen dieselbe dunkle Tinte, unabhängig von
* Material-/Bauteilfarbe, HatchStyle.color oder einem By-Layer/By-Object-
* Vordergrund-Override. `_foreground` bleibt Teil der Signatur — alle Aufrufer
* reichen weiterhin den über {@link resolveForeground} aufgelösten Bauteil-
* Vordergrund durch (z. B. für eine spätere Wiederherstellung der materialtreuen
* Schraffur) —, fließt aber bewusst nicht mehr in `color` ein.
*/
export function resolveHatch(
project: Project,
hatchId: string,
wallAngleDeg?: number,
foreground?: string,
_foreground?: string,
): HatchRender {
const h = getHatch(project, hatchId);
const ls = h.lineStyleId ? getLineStyle(project, h.lineStyleId) : null;
@@ -320,8 +321,9 @@ export function resolveHatch(
pattern: h.pattern,
scale: h.scale,
angle,
// Vordergrund-Override zuerst, sonst der deprecated HatchStyle.color-Fallback.
color: foreground ?? h.color,
// Fest auf die Monochrom-Tinte gezwungen (siehe JSDoc oben) — weder
// `_foreground` noch `HatchStyle.color` (Material-/Bauteilfarbe) fließen ein.
color: HATCH_INK,
lineWeight: ls?.weight ?? 0.13,
dash: ls?.dash ?? null,
kind: h.kind,
@@ -415,16 +417,19 @@ export function resolveStrokeWeight(
return fallback;
}
// ── SIA-Poché-Füllung ────────────────────────────────────────────────────
// ── SIA-Poché-Füllung + monochrome Schraffur-Tinte ──────────────────────────
// Die 2D-Poché einer (auch mehrschichtigen) Wand trägt NICHT die rohe Bauteil-
// Albedo (3D-Materialfarbe), sondern einen neutralen Hintergrund, auf dem die
// Schraffur-Musterlinien liegen: Vollmuster ("solid") = reine SCHWARZE Poché
// (kein Linienmuster), jedes andere Muster (inkl. "none") = WEISSER Hintergrund.
const POCHE_FILL_WHITE = "#ffffff";
const POCHE_FILL_BLACK = "#000000";
/** Neutrale Poché-Füllfarbe für ein Muster: "solid" → schwarz, sonst weiß. */
// Schraffur-Musterlinien liegen: Vollmuster ("solid") = dunkle Tinten-Poché
// (kein Linienmuster), jedes andere Muster (inkl. "none") = HELLER Hintergrund.
// Dieselben zwei Werte sind AKTUELL auch die feste Schraffur-Vordergrundfarbe
// (siehe {@link resolveHatch}) — Schnitt- wie Oberflächen-Schraffur zeigen also
// einheitlich Tinte auf hellem Papier, unabhängig von Material-/Bauteilfarbe.
export const HATCH_PAPER = "#f0f0f0";
export const HATCH_INK = "#0f0f0f";
/** Neutrale Poché-Füllfarbe für ein Muster: "solid" → Tinte, sonst Papier. */
function pocheFill(pattern: HatchPattern): string {
return pattern === "solid" ? POCHE_FILL_BLACK : POCHE_FILL_WHITE;
return pattern === "solid" ? HATCH_INK : HATCH_PAPER;
}
/** Füllfarbe + Schraffur eines aufgelösten Bauteils, fertig für eine Cut-Poché. */
@@ -459,11 +464,12 @@ export function resolveWallSectionStyle(project: Project, wall: Wall): SectionCu
wallAngleDeg,
resolveForeground(best, wall.foreground, category, wall.foregroundSource),
);
// Poché-Hintergrund neutral (weiß) statt Bauteil-Albedo; Vollmuster → schwarz.
// Poché-Hintergrund neutral (Papier) statt Bauteil-Albedo; Vollmuster → Tinte.
// `hatch.color` ist bereits über {@link resolveHatch} auf HATCH_INK gezwungen.
const solid = hatch.pattern === "solid";
return {
fill: solid ? POCHE_FILL_BLACK : POCHE_FILL_WHITE,
hatch: solid ? { ...hatch, color: POCHE_FILL_BLACK } : hatch,
fill: solid ? HATCH_INK : HATCH_PAPER,
hatch,
};
} catch {
return null;
@@ -487,14 +493,19 @@ export function resolveCeilingSectionStyle(
const category = getLayerCategory(project, ceiling.categoryCode);
const hatchId =
resolveHatchId(ceiling.hatchId, ceiling.hatchSource, category, comp.hatchId) ?? comp.hatchId;
const hatch = resolveHatch(
project,
hatchId,
undefined,
resolveForeground(comp, ceiling.foreground, category, ceiling.foregroundSource),
);
// Poché-Hintergrund neutral (Papier) statt Bauteil-Albedo; Vollmuster → Tinte
// — dieselbe Neutralisierung wie {@link resolveWallSectionStyle}, damit die
// geschnittene Decke nicht als andersfarbige Fläche gegenüber der Wand auffällt.
const solid = hatch.pattern === "solid";
return {
fill: resolveBackground(comp, ceiling.background, category, ceiling.backgroundSource),
hatch: resolveHatch(
project,
hatchId,
undefined,
resolveForeground(comp, ceiling.foreground, category, ceiling.foregroundSource),
),
fill: solid ? HATCH_INK : HATCH_PAPER,
hatch,
};
} catch {
return null;
@@ -689,25 +700,21 @@ const SECTION_HIDDEN_MM = 0.13;
/** Strichmuster verdeckter Kanten (mm Papier). */
const SECTION_HIDDEN_DASH: number[] = [0.12, 0.08];
/** Schnitt-Schraffur der Cut-Flächen (diagonale Poché wie ein Wandschnitt). */
/**
* Schnitt-Schraffur der Cut-Flächen (diagonale Poché wie ein Wandschnitt) —
* Fallback, wenn `toSection.ts` das Quell-Bauteil einer Cut-Fläche NICHT
* auflösen konnte (siehe `attachCutStyles`). Vordergrund fest `HATCH_INK`,
* damit auch der Fallback monochrom bleibt statt der rohen 3D-Albedo-Farbe.
*/
const SECTION_HATCH: HatchRender = {
pattern: "diagonal",
scale: 1,
angle: 45,
color: SECTION_INK,
color: HATCH_INK,
lineWeight: 0.13,
dash: null,
};
/** RGB (0..1) → 6-stelliges Hex, für die Cut-Poché-Füllfarbe. */
function rgbToHex(rgb: [number, number, number]): string {
const c = (v: number) =>
Math.max(0, Math.min(255, Math.round(v * 255)))
.toString(16)
.padStart(2, "0");
return `#${c(rgb[0])}${c(rgb[1])}${c(rgb[2])}`;
}
/**
* Baut aus einem {@link SectionOutput} (render3d::section, (u, v)-Meter) den
* Plan eines Schnitts/einer Ansicht. `u` wird zur Plan-X, `v` (absolute Höhe)
@@ -731,21 +738,23 @@ export function generateSectionPlan(output: SectionOutput, mono = false): Plan {
// Umrisslinie — wie eine geschnittene Wand im Grundriss. `toSection.ts`
// (computeSection) löst je Cut-Polygon bereits das Quell-Bauteil auf und
// hängt dessen echte Füllfarbe/Schraffur an (`cp.fill`/`cp.hatch`); fehlt
// diese Auflösung (Fallback), greift die generische Schnitt-Schraffur auf
// Basis der rohen Albedo-Farbe aus dem 3D-Modell.
// diese Auflösung (Fallback, z. B. verwaistes Bauteil), greift die generische
// Schnitt-Schraffur (`SECTION_HATCH`, HATCH_PAPER-Hintergrund) — NICHT die rohe
// Albedo-Farbe aus dem 3D-Modell (Nutzer-Vorgabe 2026-07-04: einheitlich
// monochrom, auch im Fallback-Fall).
for (const cp of output.cutPolygons) {
if (cp.pts.length < 3) continue;
const pts: Vec2[] = cp.pts.map(([u, v]) => {
acc(u, v);
return { x: u, y: v };
});
const fill = cp.fill ?? rgbToHex(cp.color);
const fill = cp.fill ?? HATCH_PAPER;
const hatch = cp.hatch ?? SECTION_HATCH;
// Mono: neutrale Poché — Weiss statt Bauteilfarbe, Schraffur-Musterlinien →
// Tinte. Eine schwarze SIA-Vollpoché ("solid") BLEIBT jedoch schwarz
// (Schwarz bleibt Schwarz, Weiss bleibt Weiss).
// Mono: neutrale Poché — Papier statt Bauteilfarbe, Schraffur-Musterlinien →
// Tinte. Eine dunkle SIA-Vollpoché ("solid") BLEIBT jedoch dunkel
// (Tinte bleibt Tinte, Papier bleibt Papier).
const solid = hatch.pattern === "solid";
const monoFill = solid ? POCHE_FILL_BLACK : "#ffffff";
const monoFill = solid ? HATCH_INK : HATCH_PAPER;
const monoHatch =
solid ? hatch : hatch.pattern !== "none" ? { ...hatch, color: SECTION_INK } : hatch;
primitives.push({
@@ -839,8 +848,8 @@ function toMono(primitives: Primitive[]): void {
// schwarz (Schwarz bleibt Schwarz); jede andere Füllung wird weiß, die
// Linien-/Kreuzschraffur bleibt sichtbar, aber in Tinte.
if (p.hatch.pattern === "solid") {
p.hatch = { ...p.hatch, color: POCHE_FILL_BLACK };
p.fill = p.fill === "none" ? "none" : POCHE_FILL_BLACK;
p.hatch = { ...p.hatch, color: HATCH_INK };
p.fill = p.fill === "none" ? "none" : HATCH_INK;
} else {
if (p.hatch.pattern !== "none") p.hatch = { ...p.hatch, color: MONO_INK };
p.fill = p.fill === "none" ? "none" : "#ffffff";
@@ -1146,20 +1155,21 @@ function addWallPoche(
wallAngleDeg,
resolveForeground(comp, wall.foreground, category, wall.foregroundSource),
);
// SIA-Poché: neutraler Hintergrund (weiß) statt Bauteil-Albedo, die
// Schichtschraffur liegt darüber; Vollmuster ("solid") = schwarze Poché.
// SIA-Poché: neutraler Hintergrund (Papier) statt Bauteil-Albedo, die
// Schichtschraffur (fest in HATCH_INK, siehe resolveHatch) liegt darüber;
// Vollmuster ("solid") = Tinten-Poché.
const layerSolid = layerHatch.pattern === "solid";
const layerStartCut = s <= 1e-6 ? (layerCuts ? layerCuts.start[li] : startCut) : null;
const layerEndCut = e >= axisLen - 1e-6 ? (layerCuts ? layerCuts.end[li] : endCut) : null;
out.push({
kind: "polygon",
pts: clippedBand(p1, p2, off, off + layer.thickness, layerStartCut, layerEndCut),
fill: layerSolid ? POCHE_FILL_BLACK : POCHE_FILL_WHITE,
fill: layerSolid ? HATCH_INK : HATCH_PAPER,
// Band ohne Umriss: keine Schichtfuge über die Bandkante, keine 45°-Naht
// am Knoten. Die Fugen kommen als separate `line`-Primitive (siehe unten).
stroke: "none",
strokeWidthMm: 0,
hatch: layerSolid ? { ...layerHatch, color: POCHE_FILL_BLACK } : layerHatch,
hatch: layerHatch,
greyed,
// Für die Ecktests/Konsistenz beibehalten (bei stroke:"none" wirkungslos).
noStrokeEdges: bandJoinEdges,
@@ -1254,7 +1264,7 @@ function addWallPoche(
/**
* Neutrale SIA-Poché-Füllung des tragenden Bauteils (höchste joinPriority) für
* die grobe Sammelfläche: weiß, bzw. schwarz falls dessen Schraffur "solid" ist.
* die grobe Sammelfläche: Papier, bzw. Tinte falls dessen Schraffur "solid" ist.
* (Ersetzt die frühere rohe Bauteil-Albedo — im Plan zählt die Poché, nicht die
* 3D-Materialfarbe.)
*/
@@ -1265,7 +1275,7 @@ function backbonePocheFill(project: Project, wt: Project["wallTypes"][number]):
if (!best || comp.joinPriority > best.prio)
best = { pattern: getHatch(project, comp.hatchId).pattern, prio: comp.joinPriority };
}
return best ? pocheFill(best.pattern) : POCHE_FILL_WHITE;
return best ? pocheFill(best.pattern) : HATCH_PAPER;
}
/**
@@ -1518,14 +1528,13 @@ function addCeilingPoche(
resolveForeground(comp, ceiling.foreground, category, ceiling.foregroundSource),
)
: NO_HATCH;
const solid = hatch.pattern === "solid";
out.push({
kind: "polygon",
pts,
fill: comp ? pocheFill(hatch.pattern) : "none",
stroke,
strokeWidthMm: LAYER_LINE_MM * LAYER_DETAIL_FACTOR[detail],
hatch: solid ? { ...hatch, color: POCHE_FILL_BLACK } : hatch,
hatch,
greyed,
ceilingId,
});
+10 -3
View File
@@ -22,7 +22,14 @@ import { dot, leftNormal, normalize, sub } from "../model/geometry";
import { openingInterval, openingVerticalExtent } from "../geometry/opening";
import { projectToModel3d } from "./toWalls3d";
import { loadEngine3d } from "../engine/engine3d";
import { resolveCeilingSectionStyle, resolveForeground, resolveHatch, resolveWallSectionStyle } from "./generatePlan";
import {
HATCH_INK,
HATCH_PAPER,
resolveCeilingSectionStyle,
resolveForeground,
resolveHatch,
resolveWallSectionStyle,
} from "./generatePlan";
import type { HatchRender } from "./generatePlan";
/** Bauteil-Referenz eines Cut-Polygons/einer Kante (Art + Eingabe-Index). */
@@ -516,7 +523,7 @@ function splitSlabLayers(
[uMax, vBottom],
[uMin, vBottom],
],
fill: hatch.pattern === "solid" ? "#000000" : "#ffffff",
fill: hatch.pattern === "solid" ? HATCH_INK : HATCH_PAPER,
hatch,
joinPriority: comp.joinPriority,
});
@@ -588,7 +595,7 @@ export function splitWallLayers(
[uRight, vMin],
[uLeft, vMin],
],
fill: hatch.pattern === "solid" ? "#000000" : "#ffffff",
fill: hatch.pattern === "solid" ? HATCH_INK : HATCH_PAPER,
hatch,
joinPriority: comp.joinPriority,
});