Schraffur + Schichtfugen als 0.02-mm-Haarlinien im einheitlichen Neutralschwarz
Schraffurlinien verlassen den screenPx-Sonderpfad und laufen wie jede andere Linie durch die Papier-mm-Pipeline (glPlan, SVG, PDF): im Display konstante Haarlinie (paperScaleForGl-Kehrwert von meet), im Print echte 0.02 mm - deckungsgleich mit den Schichtfugen in beiden Modi. - POCHE_STROKE #2b3039 -> #1a1a1a: kein Blaustich mehr, gleicher Neutralton wie die Schraffur (Wände/Öffnungen/Decken/Treppen). - Schichtfuge LAYER_LINE_MM 0.13 -> 0.02 (Haarlinie). - Schraffur-Stift hatch-hair/hatch-dash 0.05 -> 0.02. - Wand-Umriss Kategorie 20 lw 0.5 -> 0.35 (Standard-Aussenlinie war zu dick). - SVG-Schraffur nutzt HAIRLINE_PX/printStrokeVb statt hatchStrokePx; PDF-Export echte mm statt widthScreen.
This commit is contained in:
@@ -27,9 +27,9 @@ export const sampleProject: Project = {
|
|||||||
// Feinste Haarlinie für die SIA-Schnittschraffuren (Beton/Backstein/Dämmung):
|
// Feinste Haarlinie für die SIA-Schnittschraffuren (Beton/Backstein/Dämmung):
|
||||||
// Gewicht unter der Bildschirm-Untergrenze, damit die Musterlinien als dünnste
|
// Gewicht unter der Bildschirm-Untergrenze, damit die Musterlinien als dünnste
|
||||||
// Haarlinie (Stroke-Minimum) rendern.
|
// Haarlinie (Stroke-Minimum) rendern.
|
||||||
{ id: "hatch-hair", name: "Schraffur-Haarlinie", weight: 0.05, color: "#1a1a1a", dash: null },
|
{ id: "hatch-hair", name: "Schraffur-Haarlinie", weight: 0.02, color: "#1a1a1a", dash: null },
|
||||||
// Gestrichelte Haarlinie für die Dämmung (Striche quer durch die Wanddicke).
|
// 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] },
|
{ id: "hatch-dash", name: "Dämmungsstrich", weight: 0.02, color: "#1a1a1a", dash: [0.35, 0.3] },
|
||||||
],
|
],
|
||||||
// Schraffuren: color = Linien-/Vollfüllfarbe des Musters; angle in Grad.
|
// Schraffuren: color = Linien-/Vollfüllfarbe des Musters; angle in Grad.
|
||||||
// "none" = keine Schraffur (nur Component-Füllung).
|
// "none" = keine Schraffur (nur Component-Füllung).
|
||||||
@@ -164,7 +164,7 @@ export const sampleProject: Project = {
|
|||||||
code: "20",
|
code: "20",
|
||||||
name: "Wände",
|
name: "Wände",
|
||||||
color: "#0a0a0a",
|
color: "#0a0a0a",
|
||||||
lw: 0.5,
|
lw: 0.35,
|
||||||
visible: true,
|
visible: true,
|
||||||
locked: false,
|
locked: false,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
+37
-19
@@ -1699,7 +1699,13 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
|
|||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
{hatched.map((h) => (
|
{hatched.map((h) => (
|
||||||
<HatchPattern key={h.patternId} id={h.patternId} hatch={h.hatch} />
|
<HatchPattern
|
||||||
|
key={h.patternId}
|
||||||
|
id={h.patternId}
|
||||||
|
hatch={h.hatch}
|
||||||
|
hairline={!!hairline}
|
||||||
|
paperScale={paperScale}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</defs>
|
</defs>
|
||||||
{/* Zeichenblatt-Hintergrund: deckt den aktuellen Ausschnitt (viewBox)
|
{/* Zeichenblatt-Hintergrund: deckt den aktuellen Ausschnitt (viewBox)
|
||||||
@@ -2347,28 +2353,37 @@ function needsPattern(pattern: HatchRender["pattern"]): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Übersetzt eine Linienstärke (mm) in Bildschirm-Pixel für Musterlinien.
|
|
||||||
* Bei 0.13 mm ergibt sich ~1 px (entspricht der bisherigen Dämmungs-/
|
|
||||||
* Diagonal-Strichstärke), damit die Schraffuren wie zuvor aussehen.
|
|
||||||
*/
|
|
||||||
const hatchStrokePx = (weightMm: number): number =>
|
|
||||||
Math.max(0.6, weightMm * (1 / 0.13));
|
|
||||||
|
|
||||||
/** Strichmuster (mm) → SVG dasharray (px) oder undefined (durchgezogen). */
|
|
||||||
function dashArray(dash: number[] | null): string | undefined {
|
|
||||||
if (!dash || dash.length === 0) return undefined;
|
|
||||||
return dash.map((d) => d * (1 / 0.13)).join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ein parametrisiertes Schraffur-<pattern>. Maßstab skaliert die Kachelgröße,
|
* Ein parametrisiertes Schraffur-<pattern>. Maßstab skaliert die Kachelgröße,
|
||||||
* Winkel dreht das Muster (patternTransform), Farbe/Linienstärke kommen aus dem
|
* Winkel dreht das Muster (patternTransform), Farbe/Linienstärke kommen aus dem
|
||||||
* aufgelösten Hatch/LineStyle.
|
* aufgelösten Hatch/LineStyle. Die Musterlinie ist ein GEWÖHNLICHER Papier-mm-
|
||||||
|
* Stift (wie jede andere Linie) — dieselbe Display-/Print-Logik wie
|
||||||
|
* `renderPrimitive`: Display = konstante Haarlinie (Bildschirm-px, via
|
||||||
|
* non-scaling-stroke), Print = echte, PEN_STEPS-gequantelte Papier-mm.
|
||||||
*/
|
*/
|
||||||
function HatchPattern({ id, hatch }: { id: string; hatch: HatchRender }) {
|
function HatchPattern({
|
||||||
const sw = hatchStrokePx(hatch.lineWeight);
|
id,
|
||||||
const dashes = dashArray(hatch.dash);
|
hatch,
|
||||||
|
hairline,
|
||||||
|
paperScale,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
hatch: HatchRender;
|
||||||
|
hairline: boolean;
|
||||||
|
paperScale: number | null;
|
||||||
|
}) {
|
||||||
|
const print = !hairline && paperScale != null && paperScale > 0;
|
||||||
|
const sw = hairline
|
||||||
|
? HAIRLINE_PX
|
||||||
|
: print
|
||||||
|
? printStrokeVb(quantizePen(hatch.lineWeight), paperScale!)
|
||||||
|
: mmToPx(hatch.lineWeight);
|
||||||
|
const vfx = print ? undefined : ("non-scaling-stroke" as const);
|
||||||
|
const dashes = hatch.dash?.length
|
||||||
|
? hatch.dash
|
||||||
|
.map((mm) => (print ? printStrokeVb(mm, paperScale!) : mmToPx(mm)))
|
||||||
|
.join(" ")
|
||||||
|
: undefined;
|
||||||
|
|
||||||
if (hatch.pattern === "insulation") {
|
if (hatch.pattern === "insulation") {
|
||||||
// Weiche Zickzack-/Wellenlinie (SIA-nah). Kachel 14×10 × Maßstab.
|
// Weiche Zickzack-/Wellenlinie (SIA-nah). Kachel 14×10 × Maßstab.
|
||||||
@@ -2394,6 +2409,7 @@ function HatchPattern({ id, hatch }: { id: string; hatch: HatchRender }) {
|
|||||||
stroke={hatch.color}
|
stroke={hatch.color}
|
||||||
strokeWidth={sw}
|
strokeWidth={sw}
|
||||||
strokeDasharray={dashes}
|
strokeDasharray={dashes}
|
||||||
|
vectorEffect={vfx}
|
||||||
/>
|
/>
|
||||||
</pattern>
|
</pattern>
|
||||||
);
|
);
|
||||||
@@ -2418,6 +2434,7 @@ function HatchPattern({ id, hatch }: { id: string; hatch: HatchRender }) {
|
|||||||
stroke={hatch.color}
|
stroke={hatch.color}
|
||||||
strokeWidth={sw}
|
strokeWidth={sw}
|
||||||
strokeDasharray={dashes}
|
strokeDasharray={dashes}
|
||||||
|
vectorEffect={vfx}
|
||||||
/>
|
/>
|
||||||
{hatch.pattern === "crosshatch" && (
|
{hatch.pattern === "crosshatch" && (
|
||||||
<line
|
<line
|
||||||
@@ -2428,6 +2445,7 @@ function HatchPattern({ id, hatch }: { id: string; hatch: HatchRender }) {
|
|||||||
stroke={hatch.color}
|
stroke={hatch.color}
|
||||||
strokeWidth={sw}
|
strokeWidth={sw}
|
||||||
strokeDasharray={dashes}
|
strokeDasharray={dashes}
|
||||||
|
vectorEffect={vfx}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</pattern>
|
</pattern>
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ import { computeJoins } from "../model/joins";
|
|||||||
import type { WallCuts } from "../model/joins";
|
import type { WallCuts } from "../model/joins";
|
||||||
import type { SectionOutput } from "./toSection";
|
import type { SectionOutput } from "./toSection";
|
||||||
|
|
||||||
/** Stroke-Farbe der Schicht-Polygone (dunkles Grau). */
|
/** Stroke-Farbe der Schicht-Polygone (neutrales Schwarz, geteilte Tinte mit der Schraffur). */
|
||||||
const POCHE_STROKE = "#2b3039";
|
const POCHE_STROKE = "#1a1a1a";
|
||||||
|
|
||||||
/** Tinte für den Schwarz-Weiss-Modus: einheitliches Dunkel für alle Striche. */
|
/** Tinte für den Schwarz-Weiss-Modus: einheitliches Dunkel für alle Striche. */
|
||||||
const MONO_INK = "#111111";
|
const MONO_INK = "#111111";
|
||||||
@@ -80,8 +80,8 @@ const MONO_INK = "#111111";
|
|||||||
// dpi = 96·devicePixelRatio in konstante Bildschirm-Pixel um (non-scaling-stroke),
|
// dpi = 96·devicePixelRatio in konstante Bildschirm-Pixel um (non-scaling-stroke),
|
||||||
// damit Linien beim Zoomen ihre Papiergröße behalten und der Massstab stimmt.
|
// damit Linien beim Zoomen ihre Papiergröße behalten und der Massstab stimmt.
|
||||||
|
|
||||||
/** Dünne Trennlinie zwischen den Schichten (Schichtfugen) in mm. */
|
/** Haarlinie zwischen den Schichten (Schichtfugen) in mm — gleiche Stärke wie die Schraffur-Tinte. */
|
||||||
const LAYER_LINE_MM = 0.13;
|
const LAYER_LINE_MM = 0.02;
|
||||||
/** Stärke der Wand-Umrisslinie je Detailgrad, als Faktor auf die Ebenen-lw. */
|
/** Stärke der Wand-Umrisslinie je Detailgrad, als Faktor auf die Ebenen-lw. */
|
||||||
const OUTLINE_DETAIL_FACTOR: Record<DetailLevel, number> = {
|
const OUTLINE_DETAIL_FACTOR: Record<DetailLevel, number> = {
|
||||||
grob: 1.6, // dickere Sammellinie
|
grob: 1.6, // dickere Sammellinie
|
||||||
|
|||||||
@@ -389,25 +389,21 @@ export function compilePrimitivesToGpu(
|
|||||||
// aufs Polygon geclippt (konkav-fähig, even-odd). Musterlinien fließen als
|
// aufs Polygon geclippt (konkav-fähig, even-odd). Musterlinien fließen als
|
||||||
// zusammenhängende Läufe in `strokePolyline` → glatte, RUND gekappte/
|
// zusammenhängende Läufe in `strokePolyline` → glatte, RUND gekappte/
|
||||||
// -verbundene Übergänge (insb. die Dämmungswelle bleibt spitzenfrei).
|
// -verbundene Übergänge (insb. die Dämmungswelle bleibt spitzenfrei).
|
||||||
// BREITE — bewusst NICHT die Papier-mm-Pipeline (das ist Massstabs-N-
|
// BREITE — die Schraffur ist jetzt ein GEWÖHNLICHER Papier-mm-Stift, mit
|
||||||
// abhängig, s. `strokeMm` unten): der SVG-Referenzpfad zeichnet die
|
// derselben LineStyle-Stärke (0.02 mm) wie die Wand-Schichtfugen, und
|
||||||
// Musterlinie mit `hatchStrokePx` (PlanView.tsx) — einer FESTEN
|
// läuft durch dieselbe STANDARD-Pipeline wie jede andere Linie
|
||||||
// Bildschirm-Raum-Breite (`max(0.6, weightMm/0.13)` viewBox-px), die NUR
|
// (screenPx=false). `paperScaleForGl` liefert im Bildschirm-/Haarlinien-
|
||||||
// mit dem Zoom (nicht mit dem Papier-Massstab N) skaliert. Die Papier-mm-
|
// Modus den reziproken `meet`-Massstab, wodurch `mmToDevicePx` dort
|
||||||
// Pipeline hier stattdessen zu verwenden multipliziert die Breite
|
// zoomunabhängig konstant bleibt — die Schraffur erscheint als konstante
|
||||||
// zusätzlich mit N/1000·PX_PER_M — bei grossen/live abgeleiteten N (siehe
|
// Haarlinie wie jede Wandlinie, und im Druckmodus als echte 0.02 mm,
|
||||||
// `paperScaleForGl` in PlanView) wird die Schraffur dadurch sichtbar zu
|
// deckungsgleich mit den Schichtfugen in beiden Modi.
|
||||||
// DICK. `screenPx=true` markiert den Batch dafür, dass der Renderer NUR
|
|
||||||
// mit `meet` (Zoom) statt mit `mmToDevicePx` (Papier-Massstab) skaliert —
|
|
||||||
// exakt der SVG-Formel entsprechend (siehe glPlanRender.drawLines).
|
|
||||||
// `hatch.dash` wird geometrisch (Strich/Lücke) aufgelöst, da die GL-
|
// `hatch.dash` wird geometrisch (Strich/Lücke) aufgelöst, da die GL-
|
||||||
// Linien-Pipeline kein Dash kennt.
|
// Linien-Pipeline kein Dash kennt.
|
||||||
if (prim.hatch && prim.hatch.pattern !== 'none' && prim.hatch.pattern !== 'solid') {
|
if (prim.hatch && prim.hatch.pattern !== 'none' && prim.hatch.pattern !== 'solid') {
|
||||||
const hatchColor = parseColor(prim.hatch.color) ?? [0.1, 0.1, 0.1, 1];
|
const hatchColor = parseColor(prim.hatch.color) ?? [0.1, 0.1, 0.1, 1];
|
||||||
const hatchMm = prim.hatch.lineWeight > 0 ? prim.hatch.lineWeight : 0.13;
|
const hatchMm = prim.hatch.lineWeight > 0 ? prim.hatch.lineWeight : 0.13;
|
||||||
const hatchStrokeVb = Math.max(0.6, hatchMm * (1 / 0.13));
|
|
||||||
const runs = applyDashRuns(buildHatchRuns(prim.pts, prim.hatch), prim.hatch.dash);
|
const runs = applyDashRuns(buildHatchRuns(prim.pts, prim.hatch), prim.hatch.dash);
|
||||||
for (const run of runs) strokePolyline(run, false, hatchColor, hatchStrokeVb, true);
|
for (const run of runs) strokePolyline(run, false, hatchColor, hatchMm, false);
|
||||||
}
|
}
|
||||||
// Umriss (crispe Kante) — geschlossener Ring, GEHRT (kein Stufen-Cap an
|
// Umriss (crispe Kante) — geschlossener Ring, GEHRT (kein Stufen-Cap an
|
||||||
// Ecken). Breite = ECHTE Papier-mm; der Renderer rechnet massstabs-/
|
// Ecken). Breite = ECHTE Papier-mm; der Renderer rechnet massstabs-/
|
||||||
|
|||||||
@@ -193,13 +193,13 @@ export class PlanGpuRenderer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Striche. Zwei Skalierungs-Regime pro Batch (siehe `LineBatch.screenPx`):
|
* Striche. Zwei Skalierungs-Regime pro Batch (siehe `LineBatch.screenPx`):
|
||||||
* • Papier-mm (Standard) — Wand-/Objekt-Umrisse: massstabs- + zoomrichtig
|
* • Papier-mm (Standard) — Wand-/Objekt-Umrisse UND Schraffur-Musterlinien
|
||||||
* über `mmToDevicePx`.
|
* (letztere seit dem Haarlinien-Fix ein gewöhnlicher 0.02-mm-Stift wie
|
||||||
* • Bildschirm-px (`screenPx`) — Schraffur-Musterlinien: NUR mit `meet`
|
* die Wand-Schichtfugen): massstabs- + zoomrichtig über `mmToDevicePx`.
|
||||||
* (Zoom) skaliert, unabhängig vom Papier-Massstab N — exakt wie der SVG-
|
* • Bildschirm-px (`screenPx`) — für etwaige künftige rein bildschirm-
|
||||||
* `hatchStrokePx`-Pfad (`patternUnits="userSpaceOnUse"`, keine
|
* gekoppelte Linien: NUR mit `meet` (Zoom) skaliert, unabhängig vom
|
||||||
* `printStrokeVb`-Umrechnung) und der PDF-Export (`toRenderScene.ts`,
|
* Papier-Massstab N. Aktuell von keinem Batch mehr genutzt, Infrastruktur
|
||||||
* `widthScreen: true`).
|
* bleibt erhalten.
|
||||||
*/
|
*/
|
||||||
private drawLines(
|
private drawLines(
|
||||||
geometry: GpuGeometry,
|
geometry: GpuGeometry,
|
||||||
|
|||||||
@@ -57,8 +57,10 @@ export interface RPolyline {
|
|||||||
z: number;
|
z: number;
|
||||||
/**
|
/**
|
||||||
* true: `widthMm` trägt BILDSCHIRM-Einheiten (viewBox-px, zoom-skalierend)
|
* true: `widthMm` trägt BILDSCHIRM-Einheiten (viewBox-px, zoom-skalierend)
|
||||||
* statt Papier-mm — für Schraffur-Striche, deren Breite im SVG an die
|
* statt Papier-mm. Aktuell von keinem Primitiv mehr gesetzt — auch die
|
||||||
* Musterkachel gekoppelt ist (`hatchStrokePx`), nicht an Papier-mm.
|
* Schraffur läuft inzwischen als gewöhnlicher Papier-mm-Stift (0.02 mm,
|
||||||
|
* gleiche Tinte wie die Wand-Schichtfugen). Feld bleibt für etwaige
|
||||||
|
* zukünftige Bildschirm-gekoppelte Linien erhalten.
|
||||||
*/
|
*/
|
||||||
widthScreen?: boolean;
|
widthScreen?: boolean;
|
||||||
}
|
}
|
||||||
@@ -376,11 +378,10 @@ export function planToRenderScene(plan: Plan, paperScaleN: number = STAMP_DEFAUL
|
|||||||
undefined,
|
undefined,
|
||||||
p.greyed,
|
p.greyed,
|
||||||
);
|
);
|
||||||
// Strichbreite wie das SVG-Muster (`hatchStrokePx`): in viewBox-px, an
|
// Strichbreite: ein GEWÖHNLICHER Papier-mm-Stift (0.02 mm), wie jede
|
||||||
// die Kachel gekoppelt und mit dem Zoom skalierend — NICHT Papier-mm
|
// andere Linie — nicht mehr an die Musterkachel/Bildschirm-px
|
||||||
// (sonst wird die Schraffur bei kleinen Masstäben zur Haarlinie).
|
// gekoppelt (siehe RPolyline.widthScreen-Kommentar).
|
||||||
const hatchMm = p.hatch.lineWeight > 0 ? p.hatch.lineWeight : 0.13;
|
const hatchMm = p.hatch.lineWeight > 0 ? p.hatch.lineWeight : 0.13;
|
||||||
const hatchPx = Math.max(0.6, hatchMm * (1 / 0.13));
|
|
||||||
const runs = applyDashRuns(buildHatchRuns(p.pts, p.hatch), p.hatch.dash);
|
const runs = applyDashRuns(buildHatchRuns(p.pts, p.hatch), p.hatch.dash);
|
||||||
// Alle Musterläufe eines Polygons teilen EIN z (überlappen einander nicht).
|
// Alle Musterläufe eines Polygons teilen EIN z (überlappen einander nicht).
|
||||||
const hatchZ = zc++;
|
const hatchZ = zc++;
|
||||||
@@ -389,8 +390,7 @@ export function planToRenderScene(plan: Plan, paperScaleN: number = STAMP_DEFAUL
|
|||||||
polylines.push({
|
polylines.push({
|
||||||
pts: run.map((v) => [v.x, v.y]),
|
pts: run.map((v) => [v.x, v.y]),
|
||||||
color: hatchCol,
|
color: hatchCol,
|
||||||
widthMm: hatchPx,
|
widthMm: hatchMm,
|
||||||
widthScreen: true,
|
|
||||||
z: hatchZ,
|
z: hatchZ,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user