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:
+44
-1
@@ -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"],
|
||||
|
||||
@@ -510,6 +510,8 @@ export const de = {
|
||||
"resources.col.pattern": "Muster",
|
||||
"resources.col.scale": "Maßstab",
|
||||
"resources.col.angle": "Winkel °",
|
||||
"resources.col.relativeToWall": "Wandbezug",
|
||||
"resources.col.relativeToWall.hint": "Winkel relativ zur Wandachse — das Muster dreht mit der Wand mit",
|
||||
"resources.col.lineStyle": "Linienstil",
|
||||
"resources.lineStyle.none": "— ohne —",
|
||||
"resources.delete.hatch": "Schraffur „{name}\" löschen",
|
||||
|
||||
@@ -505,6 +505,8 @@ export const en: Record<TranslationKey, string> = {
|
||||
"resources.col.pattern": "Pattern",
|
||||
"resources.col.scale": "Scale",
|
||||
"resources.col.angle": "Angle °",
|
||||
"resources.col.relativeToWall": "Wall-rel.",
|
||||
"resources.col.relativeToWall.hint": "Angle relative to the wall axis — the pattern rotates with the wall",
|
||||
"resources.col.lineStyle": "Line style",
|
||||
"resources.lineStyle.none": "— none —",
|
||||
"resources.delete.hatch": "Delete hatch “{name}”",
|
||||
|
||||
@@ -24,6 +24,12 @@ export const sampleProject: Project = {
|
||||
{ id: "thick", name: "Stark 0.5", weight: 0.5, color: "#0a0a0a", dash: null },
|
||||
// Schraffurlinie = schwarze Haarlinie (Grundeinstellung für alle Schraffuren).
|
||||
{ id: "hatch-line", name: "Schraffurlinie", weight: 0.13, color: "#1a1a1a", dash: null },
|
||||
// Feinste Haarlinie für die SIA-Schnittschraffuren (Beton/Backstein/Dämmung):
|
||||
// Gewicht unter der Bildschirm-Untergrenze, damit die Musterlinien als dünnste
|
||||
// Haarlinie (Stroke-Minimum) rendern.
|
||||
{ id: "hatch-hair", name: "Schraffur-Haarlinie", weight: 0.05, color: "#1a1a1a", dash: null },
|
||||
// Gestrichelte Haarlinie für die Dämmung (Striche quer durch die Wanddicke).
|
||||
{ id: "hatch-dash", name: "Dämmungsstrich", weight: 0.05, color: "#1a1a1a", dash: [0.35, 0.3] },
|
||||
],
|
||||
// Schraffuren: color = Linien-/Vollfüllfarbe des Musters; angle in Grad.
|
||||
// "none" = keine Schraffur (nur Component-Füllung).
|
||||
@@ -67,17 +73,53 @@ export const sampleProject: Project = {
|
||||
color: "#9aa0a6",
|
||||
lineStyleId: "thin",
|
||||
},
|
||||
// ── SIA-Schnittschraffuren (Haarlinie) ──────────────────────────────────
|
||||
// Dichte Teilung (scale < 1; Muster-Kachel = 8·scale) für den SIA-Look.
|
||||
// Beton: Kreuzschraffur, absolut 45° (bildschirmfest, NICHT wandbezogen).
|
||||
{
|
||||
id: "sia-concrete",
|
||||
name: "Beton (Kreuz)",
|
||||
pattern: "crosshatch",
|
||||
scale: 0.5,
|
||||
angle: 45,
|
||||
color: "#1a1a1a",
|
||||
lineStyleId: "hatch-hair",
|
||||
},
|
||||
// Backstein: Diagonale, 45° RELATIV zur Wandachse (dreht mit der Wand mit).
|
||||
{
|
||||
id: "sia-brick",
|
||||
name: "Backstein (Diagonal)",
|
||||
pattern: "diagonal",
|
||||
scale: 0.5,
|
||||
angle: 45,
|
||||
relativeToWall: true,
|
||||
color: "#1a1a1a",
|
||||
lineStyleId: "hatch-hair",
|
||||
},
|
||||
// Dämmung: DURCHGEZOGENE (nicht gestrichelte) Linien QUER durch die Wanddicke
|
||||
// (senkrecht zur Wandachse), wandbezogen. Basiswinkel 0 = Musterlinie steht
|
||||
// senkrecht zur Wandrichtung; durchgezogene Haarlinie, dichte Teilung.
|
||||
{
|
||||
id: "sia-insulation",
|
||||
name: "Dämmung (Strich)",
|
||||
pattern: "diagonal",
|
||||
scale: 0.45,
|
||||
angle: 0,
|
||||
relativeToWall: true,
|
||||
color: "#1a1a1a",
|
||||
lineStyleId: "hatch-hair",
|
||||
},
|
||||
],
|
||||
// Bauteil-Materialien: color = Poché-Füllung im Grundriss UND 3D-Diffusfarbe;
|
||||
// joinPriority höher = läuft am Stoß durch (für spätere T-Stöße).
|
||||
components: [
|
||||
{ id: "render-ext", name: "Aussenputz", color: "#d8d2c7", hatchId: "none", joinPriority: 10 },
|
||||
// Weißer Grund unter der schwarzen Dämmungs-Haarlinie (Schraffur-Grundeinstellung).
|
||||
{ id: "insulation", name: "Wärmedämmung", color: "#ffffff", hatchId: "insulation", joinPriority: 20 },
|
||||
{ id: "brick", name: "Backstein", color: "#8c5544", hatchId: "none", joinPriority: 50 },
|
||||
// Weißer Grund unter den durchgezogenen Dämmungs-Haarlinien (quer zur Wand).
|
||||
{ id: "insulation", name: "Wärmedämmung", color: "#ffffff", hatchId: "sia-insulation", joinPriority: 20 },
|
||||
{ id: "brick", name: "Backstein", color: "#8c5544", hatchId: "sia-brick", joinPriority: 50 },
|
||||
{ id: "render-int", name: "Innenputz", color: "#efece6", hatchId: "none", joinPriority: 10 },
|
||||
// Für spätere T-Stöße: Beton als durchlaufender Backbone (höchste Priorität).
|
||||
{ id: "concrete", name: "Beton", color: "#9aa0a6", hatchId: "solid-concrete", joinPriority: 100 },
|
||||
{ id: "concrete", name: "Beton", color: "#9aa0a6", hatchId: "sia-concrete", joinPriority: 100 },
|
||||
],
|
||||
wallTypes: [
|
||||
{
|
||||
|
||||
@@ -55,6 +55,14 @@ export interface HatchStyle {
|
||||
scale: number;
|
||||
/** Drehung des Musters in Grad. */
|
||||
angle: number;
|
||||
/**
|
||||
* Wenn `true`, ist `angle` NICHT bildschirmfest, sondern relativ zur Achse der
|
||||
* schraffierten Wand: das Muster dreht mit der Wandorientierung mit (z. B. eine
|
||||
* Diagonale, die stets 45° zur Wand steht, oder Dämmungsstriche quer durch die
|
||||
* Wanddicke). Nur die Wand-Poché wertet dies aus; ohne Wandkontext (z. B. Decke)
|
||||
* degradiert es zu einem absoluten `angle`.
|
||||
*/
|
||||
relativeToWall?: boolean;
|
||||
/**
|
||||
* Farbe der Musterlinien bzw. der Vollfüllung (`pattern==="solid"`). Bei
|
||||
* `pattern==="none"` ungenutzt.
|
||||
|
||||
+75
-26
@@ -242,19 +242,40 @@ export type Primitive =
|
||||
* {@link HatchRender} auf. Der Linienstil der Musterlinien wird, falls gesetzt,
|
||||
* aus dem Line Manager nachgeschlagen; sonst gilt ein dünner Default.
|
||||
*/
|
||||
function resolveHatch(project: Project, hatchId: string): HatchRender {
|
||||
function resolveHatch(project: Project, hatchId: string, wallAngleDeg?: number): HatchRender {
|
||||
const h = getHatch(project, hatchId);
|
||||
const ls = h.lineStyleId ? getLineStyle(project, h.lineStyleId) : null;
|
||||
// Wandbezogener Winkel: `HatchRender.angle` wird von allen Renderern in der
|
||||
// SVG-Konvention (im Uhrzeigersinn, Bildschirm-Y nach unten) verstanden — der
|
||||
// Modell-Renderer negiert ihn in `toModelAngleRad`. Die Wandachse `wallAngleDeg`
|
||||
// ist dagegen ein MODELL-Winkel (atan2 der Richtung, gegen den Uhrzeigersinn,
|
||||
// Y nach oben). Wegen der Y-Spiegelung entspricht die Wandrichtung am Bildschirm
|
||||
// dem Winkel −wallAngleDeg; das Muster folgt der Wand also, indem der
|
||||
// Wandwinkel SUBTRAHIERT wird. Ohne Wandkontext (Decke) bleibt `angle` absolut.
|
||||
const angle =
|
||||
h.relativeToWall && wallAngleDeg != null ? h.angle - wallAngleDeg : h.angle;
|
||||
return {
|
||||
pattern: h.pattern,
|
||||
scale: h.scale,
|
||||
angle: h.angle,
|
||||
angle,
|
||||
color: h.color,
|
||||
lineWeight: ls?.weight ?? 0.13,
|
||||
dash: ls?.dash ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
// ── SIA-Poché-Füllung ────────────────────────────────────────────────────
|
||||
// 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ß. */
|
||||
function pocheFill(pattern: HatchPattern): string {
|
||||
return pattern === "solid" ? POCHE_FILL_BLACK : POCHE_FILL_WHITE;
|
||||
}
|
||||
|
||||
/** Füllfarbe + Schraffur eines aufgelösten Bauteils, fertig für eine Cut-Poché. */
|
||||
export interface SectionCutStyle {
|
||||
fill: string;
|
||||
@@ -263,8 +284,8 @@ export interface SectionCutStyle {
|
||||
|
||||
/**
|
||||
* Löst das tragende Bauteil (höchste `joinPriority`) einer Wand auf — dieselbe
|
||||
* Regel wie {@link backboneColor} für die grobe Grundriss-Poché — und liefert
|
||||
* dessen Füllfarbe + echte Schraffur für die Schnitt-Poché. `null`, wenn Wandtyp
|
||||
* Regel wie {@link backbonePocheFill} für die grobe Grundriss-Poché — und liefert
|
||||
* dessen Poché-Füllung + echte Schraffur für die Schnitt-Poché. `null`, wenn Wandtyp
|
||||
* oder Bauteil nicht auflösbar sind (z. B. verwaistes `wallTypeId`); der
|
||||
* Aufrufer fällt dann auf die generische Schnitt-Schraffur zurück.
|
||||
*/
|
||||
@@ -277,7 +298,15 @@ export function resolveWallSectionStyle(project: Project, wall: Wall): SectionCu
|
||||
if (!best || comp.joinPriority > best.joinPriority) best = comp;
|
||||
}
|
||||
if (!best) return null;
|
||||
return { fill: best.color, hatch: resolveHatch(project, best.hatchId) };
|
||||
const wallAngleDeg =
|
||||
(Math.atan2(wall.end.y - wall.start.y, wall.end.x - wall.start.x) * 180) / Math.PI;
|
||||
const hatch = resolveHatch(project, best.hatchId, wallAngleDeg);
|
||||
// Poché-Hintergrund neutral (weiß) statt Bauteil-Albedo; Vollmuster → schwarz.
|
||||
const solid = hatch.pattern === "solid";
|
||||
return {
|
||||
fill: solid ? POCHE_FILL_BLACK : POCHE_FILL_WHITE,
|
||||
hatch: solid ? { ...hatch, color: POCHE_FILL_BLACK } : hatch,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -528,17 +557,20 @@ export function generateSectionPlan(output: SectionOutput, mono = false): Plan {
|
||||
});
|
||||
const fill = cp.fill ?? rgbToHex(cp.color);
|
||||
const hatch = cp.hatch ?? SECTION_HATCH;
|
||||
// Mono: reines Weiss statt Bauteilfarbe, Schraffurfarbe → Tinte (nur
|
||||
// Umriss + Schraffur-Musterlinien tragen; wie der toMono()-Postpass für
|
||||
// die normale Poché, hier direkt inline, da Cut-Polygone kein "solid"
|
||||
// führen).
|
||||
// Mono: neutrale Poché — Weiss statt Bauteilfarbe, Schraffur-Musterlinien →
|
||||
// Tinte. Eine schwarze SIA-Vollpoché ("solid") BLEIBT jedoch schwarz
|
||||
// (Schwarz bleibt Schwarz, Weiss bleibt Weiss).
|
||||
const solid = hatch.pattern === "solid";
|
||||
const monoFill = solid ? POCHE_FILL_BLACK : "#ffffff";
|
||||
const monoHatch =
|
||||
solid ? hatch : hatch.pattern !== "none" ? { ...hatch, color: SECTION_INK } : hatch;
|
||||
primitives.push({
|
||||
kind: "polygon",
|
||||
pts,
|
||||
fill: mono ? "#ffffff" : fill,
|
||||
fill: mono ? monoFill : fill,
|
||||
stroke: SECTION_INK,
|
||||
strokeWidthMm: SECTION_CUT_OUTLINE_MM,
|
||||
hatch: mono && hatch.pattern !== "none" ? { ...hatch, color: SECTION_INK } : hatch,
|
||||
hatch: mono ? monoHatch : hatch,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -619,14 +651,16 @@ function addContextContours(out: Primitive[], project: Project): void {
|
||||
function toMono(primitives: Primitive[]): void {
|
||||
for (const p of primitives) {
|
||||
if (p.kind === "polygon") {
|
||||
// Vollfüllung „solid" deckte die Poché farbig — in mono auf Weiss setzen,
|
||||
// damit nur der Umriss trägt. Linien-/Kreuzschraffur bleibt, aber in Tinte.
|
||||
// SIA-Poché in mono: Vollmuster „solid" ist eine SCHWARZE Poché und bleibt
|
||||
// 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: "#ffffff" };
|
||||
} else if (p.hatch.pattern !== "none") {
|
||||
p.hatch = { ...p.hatch, color: MONO_INK };
|
||||
p.hatch = { ...p.hatch, color: POCHE_FILL_BLACK };
|
||||
p.fill = p.fill === "none" ? "none" : POCHE_FILL_BLACK;
|
||||
} else {
|
||||
if (p.hatch.pattern !== "none") p.hatch = { ...p.hatch, color: MONO_INK };
|
||||
p.fill = p.fill === "none" ? "none" : "#ffffff";
|
||||
}
|
||||
p.fill = p.fill === "none" ? "none" : "#ffffff";
|
||||
p.stroke = MONO_INK;
|
||||
} else if (p.kind === "line") {
|
||||
p.color = MONO_INK;
|
||||
@@ -801,6 +835,11 @@ function addWallPoche(
|
||||
// GANZE Wand (alle Bänder) markiert und die PlanView die wallId nach oben
|
||||
// melden kann.
|
||||
const wallId = wall.id;
|
||||
// Achswinkel der Wand (Modell, Grad) — treibt wandbezogene Schraffuren
|
||||
// ({@link resolveHatch}), damit z. B. eine Diagonale oder die Dämmungsstriche
|
||||
// mit der Wandorientierung mitdrehen.
|
||||
const wallAngleDeg =
|
||||
(Math.atan2(wall.end.y - wall.start.y, wall.end.x - wall.start.x) * 180) / Math.PI;
|
||||
// Strich-/Umrandungsfarbe der Wand: optionale Übersteuerung (wall.color),
|
||||
// sonst der Default-Poché-Strich. Betrifft NUR die Linienfarbe (Umriss +
|
||||
// Schichtfugen), nicht die Schicht-Füllfarben/Schraffuren.
|
||||
@@ -821,8 +860,9 @@ function addWallPoche(
|
||||
|
||||
const outlineMm = wallLwMm * OUTLINE_DETAIL_FACTOR[detail];
|
||||
const layerLineMm = LAYER_LINE_MM * LAYER_DETAIL_FACTOR[detail];
|
||||
// Vereinfachte Füllung (grob): Farbe des tragenden Bauteils (höchste Priorität).
|
||||
const simpleFill = backboneColor(project, wt);
|
||||
// Vereinfachte Füllung (grob): neutrale SIA-Poché des tragenden Bauteils
|
||||
// (höchste Priorität) — weiß, bzw. schwarz falls dessen Muster "solid" ist.
|
||||
const simpleFill = backbonePocheFill(project, wt);
|
||||
|
||||
// Kanten-Indizes der inneren Gehrungs-Stirnflächen (die Diagonalen am Knoten).
|
||||
// clippedBand liefert [A.start, A.end, B.end, B.start] → Kante 1 (A.end→B.end)
|
||||
@@ -868,13 +908,17 @@ function addWallPoche(
|
||||
let off = refOff - total / 2;
|
||||
for (const layer of wt.layers) {
|
||||
const comp = getComponent(project, layer.componentId);
|
||||
const layerHatch = resolveHatch(project, comp.hatchId, wallAngleDeg);
|
||||
// SIA-Poché: neutraler Hintergrund (weiß) statt Bauteil-Albedo, die
|
||||
// Schichtschraffur liegt darüber; Vollmuster ("solid") = schwarze Poché.
|
||||
const layerSolid = layerHatch.pattern === "solid";
|
||||
out.push({
|
||||
kind: "polygon",
|
||||
pts: clippedBand(p1, p2, off, off + layer.thickness, startCut, endCut),
|
||||
fill: comp.color,
|
||||
fill: layerSolid ? POCHE_FILL_BLACK : POCHE_FILL_WHITE,
|
||||
stroke,
|
||||
strokeWidthMm: layerLineMm,
|
||||
hatch: resolveHatch(project, comp.hatchId),
|
||||
hatch: layerSolid ? { ...layerHatch, color: POCHE_FILL_BLACK } : layerHatch,
|
||||
greyed,
|
||||
// Die Diagonale am Knoten NICHT stricheln → keine 45°-Naht zwischen
|
||||
// gleichfarbigen Schichten der beiden Wände.
|
||||
@@ -900,15 +944,20 @@ function addWallPoche(
|
||||
}
|
||||
}
|
||||
|
||||
/** Farbe des tragenden Bauteils (höchste joinPriority) für die grobe Poché. */
|
||||
function backboneColor(project: Project, wt: Project["wallTypes"][number]): string {
|
||||
let best: { color: string; prio: number } | null = null;
|
||||
/**
|
||||
* 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.
|
||||
* (Ersetzt die frühere rohe Bauteil-Albedo — im Plan zählt die Poché, nicht die
|
||||
* 3D-Materialfarbe.)
|
||||
*/
|
||||
function backbonePocheFill(project: Project, wt: Project["wallTypes"][number]): string {
|
||||
let best: { pattern: HatchPattern; prio: number } | null = null;
|
||||
for (const layer of wt.layers) {
|
||||
const comp = getComponent(project, layer.componentId);
|
||||
if (!best || comp.joinPriority > best.prio)
|
||||
best = { color: comp.color, prio: comp.joinPriority };
|
||||
best = { pattern: getHatch(project, comp.hatchId).pattern, prio: comp.joinPriority };
|
||||
}
|
||||
return best?.color ?? "#9aa0a6";
|
||||
return best ? pocheFill(best.pattern) : POCHE_FILL_WHITE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -860,12 +860,13 @@ const HATCH_COLUMNS: ResColumn[] = [
|
||||
{ titleKey: "resources.col.pattern" },
|
||||
{ titleKey: "resources.col.scale", align: "right" },
|
||||
{ titleKey: "resources.col.angle", align: "right" },
|
||||
{ titleKey: "resources.col.relativeToWall" },
|
||||
{ titleKey: "resources.col.color" },
|
||||
{ titleKey: "resources.col.lineStyle" },
|
||||
{ titleKey: "" },
|
||||
];
|
||||
const HATCH_TEMPLATE =
|
||||
"minmax(110px, 1fr) minmax(120px, 0.9fr) 64px 60px auto minmax(120px, 0.9fr) 36px";
|
||||
"minmax(110px, 1fr) minmax(120px, 0.9fr) 64px 60px 64px auto minmax(120px, 0.9fr) 36px";
|
||||
|
||||
function HatchRow({
|
||||
hatch,
|
||||
@@ -918,6 +919,14 @@ function HatchRow({
|
||||
step={5}
|
||||
/>
|
||||
</ResCell>
|
||||
<ResCell>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!hatch.relativeToWall}
|
||||
onChange={(e) => onPatch({ relativeToWall: e.target.checked })}
|
||||
title={t("resources.col.relativeToWall.hint")}
|
||||
/>
|
||||
</ResCell>
|
||||
<ResCell>
|
||||
<ColorField color={hatch.color} onChange={(color) => onPatch({ color })} />
|
||||
</ResCell>
|
||||
|
||||
Reference in New Issue
Block a user