diff --git a/src/model/sampleProject.ts b/src/model/sampleProject.ts index 9b4e466..9890c09 100644 --- a/src/model/sampleProject.ts +++ b/src/model/sampleProject.ts @@ -27,9 +27,9 @@ export const sampleProject: Project = { // 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 }, + { 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). - { 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. // "none" = keine Schraffur (nur Component-Füllung). @@ -164,7 +164,7 @@ export const sampleProject: Project = { code: "20", name: "Wände", color: "#0a0a0a", - lw: 0.5, + lw: 0.35, visible: true, locked: false, children: [ diff --git a/src/plan/PlanView.tsx b/src/plan/PlanView.tsx index 32feb34..120e4ac 100644 --- a/src/plan/PlanView.tsx +++ b/src/plan/PlanView.tsx @@ -1699,7 +1699,13 @@ export const PlanView = forwardRef( > {hatched.map((h) => ( - + ))} {/* 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-. Maßstab skaliert die Kachelgröße, * 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 }) { - const sw = hatchStrokePx(hatch.lineWeight); - const dashes = dashArray(hatch.dash); +function HatchPattern({ + id, + 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") { // 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} strokeWidth={sw} strokeDasharray={dashes} + vectorEffect={vfx} /> ); @@ -2418,6 +2434,7 @@ function HatchPattern({ id, hatch }: { id: string; hatch: HatchRender }) { stroke={hatch.color} strokeWidth={sw} strokeDasharray={dashes} + vectorEffect={vfx} /> {hatch.pattern === "crosshatch" && ( )} diff --git a/src/plan/generatePlan.ts b/src/plan/generatePlan.ts index 9cfdd2a..403a855 100644 --- a/src/plan/generatePlan.ts +++ b/src/plan/generatePlan.ts @@ -69,8 +69,8 @@ import { computeJoins } from "../model/joins"; import type { WallCuts } from "../model/joins"; import type { SectionOutput } from "./toSection"; -/** Stroke-Farbe der Schicht-Polygone (dunkles Grau). */ -const POCHE_STROKE = "#2b3039"; +/** Stroke-Farbe der Schicht-Polygone (neutrales Schwarz, geteilte Tinte mit der Schraffur). */ +const POCHE_STROKE = "#1a1a1a"; /** Tinte für den Schwarz-Weiss-Modus: einheitliches Dunkel für alle Striche. */ const MONO_INK = "#111111"; @@ -80,8 +80,8 @@ const MONO_INK = "#111111"; // dpi = 96·devicePixelRatio in konstante Bildschirm-Pixel um (non-scaling-stroke), // damit Linien beim Zoomen ihre Papiergröße behalten und der Massstab stimmt. -/** Dünne Trennlinie zwischen den Schichten (Schichtfugen) in mm. */ -const LAYER_LINE_MM = 0.13; +/** Haarlinie zwischen den Schichten (Schichtfugen) in mm — gleiche Stärke wie die Schraffur-Tinte. */ +const LAYER_LINE_MM = 0.02; /** Stärke der Wand-Umrisslinie je Detailgrad, als Faktor auf die Ebenen-lw. */ const OUTLINE_DETAIL_FACTOR: Record = { grob: 1.6, // dickere Sammellinie diff --git a/src/plan/glPlan/glPlanCompile.ts b/src/plan/glPlan/glPlanCompile.ts index a3fbba8..78fb534 100644 --- a/src/plan/glPlan/glPlanCompile.ts +++ b/src/plan/glPlan/glPlanCompile.ts @@ -389,25 +389,21 @@ export function compilePrimitivesToGpu( // aufs Polygon geclippt (konkav-fähig, even-odd). Musterlinien fließen als // zusammenhängende Läufe in `strokePolyline` → glatte, RUND gekappte/ // -verbundene Übergänge (insb. die Dämmungswelle bleibt spitzenfrei). - // BREITE — bewusst NICHT die Papier-mm-Pipeline (das ist Massstabs-N- - // abhängig, s. `strokeMm` unten): der SVG-Referenzpfad zeichnet die - // Musterlinie mit `hatchStrokePx` (PlanView.tsx) — einer FESTEN - // Bildschirm-Raum-Breite (`max(0.6, weightMm/0.13)` viewBox-px), die NUR - // mit dem Zoom (nicht mit dem Papier-Massstab N) skaliert. Die Papier-mm- - // Pipeline hier stattdessen zu verwenden multipliziert die Breite - // zusätzlich mit N/1000·PX_PER_M — bei grossen/live abgeleiteten N (siehe - // `paperScaleForGl` in PlanView) wird die Schraffur dadurch sichtbar zu - // 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). + // BREITE — die Schraffur ist jetzt ein GEWÖHNLICHER Papier-mm-Stift, mit + // derselben LineStyle-Stärke (0.02 mm) wie die Wand-Schichtfugen, und + // läuft durch dieselbe STANDARD-Pipeline wie jede andere Linie + // (screenPx=false). `paperScaleForGl` liefert im Bildschirm-/Haarlinien- + // Modus den reziproken `meet`-Massstab, wodurch `mmToDevicePx` dort + // zoomunabhängig konstant bleibt — die Schraffur erscheint als konstante + // Haarlinie wie jede Wandlinie, und im Druckmodus als echte 0.02 mm, + // deckungsgleich mit den Schichtfugen in beiden Modi. // `hatch.dash` wird geometrisch (Strich/Lücke) aufgelöst, da die GL- // Linien-Pipeline kein Dash kennt. 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 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); - 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 // Ecken). Breite = ECHTE Papier-mm; der Renderer rechnet massstabs-/ diff --git a/src/plan/glPlan/glPlanRender.ts b/src/plan/glPlan/glPlanRender.ts index 3d38d74..4ccd23a 100644 --- a/src/plan/glPlan/glPlanRender.ts +++ b/src/plan/glPlan/glPlanRender.ts @@ -193,13 +193,13 @@ export class PlanGpuRenderer { /** * Striche. Zwei Skalierungs-Regime pro Batch (siehe `LineBatch.screenPx`): - * • Papier-mm (Standard) — Wand-/Objekt-Umrisse: massstabs- + zoomrichtig - * über `mmToDevicePx`. - * • Bildschirm-px (`screenPx`) — Schraffur-Musterlinien: NUR mit `meet` - * (Zoom) skaliert, unabhängig vom Papier-Massstab N — exakt wie der SVG- - * `hatchStrokePx`-Pfad (`patternUnits="userSpaceOnUse"`, keine - * `printStrokeVb`-Umrechnung) und der PDF-Export (`toRenderScene.ts`, - * `widthScreen: true`). + * • Papier-mm (Standard) — Wand-/Objekt-Umrisse UND Schraffur-Musterlinien + * (letztere seit dem Haarlinien-Fix ein gewöhnlicher 0.02-mm-Stift wie + * die Wand-Schichtfugen): massstabs- + zoomrichtig über `mmToDevicePx`. + * • Bildschirm-px (`screenPx`) — für etwaige künftige rein bildschirm- + * gekoppelte Linien: NUR mit `meet` (Zoom) skaliert, unabhängig vom + * Papier-Massstab N. Aktuell von keinem Batch mehr genutzt, Infrastruktur + * bleibt erhalten. */ private drawLines( geometry: GpuGeometry, diff --git a/src/plan/toRenderScene.ts b/src/plan/toRenderScene.ts index e51a624..d77e986 100644 --- a/src/plan/toRenderScene.ts +++ b/src/plan/toRenderScene.ts @@ -57,8 +57,10 @@ export interface RPolyline { z: number; /** * true: `widthMm` trägt BILDSCHIRM-Einheiten (viewBox-px, zoom-skalierend) - * statt Papier-mm — für Schraffur-Striche, deren Breite im SVG an die - * Musterkachel gekoppelt ist (`hatchStrokePx`), nicht an Papier-mm. + * statt Papier-mm. Aktuell von keinem Primitiv mehr gesetzt — auch die + * 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; } @@ -376,11 +378,10 @@ export function planToRenderScene(plan: Plan, paperScaleN: number = STAMP_DEFAUL undefined, p.greyed, ); - // Strichbreite wie das SVG-Muster (`hatchStrokePx`): in viewBox-px, an - // die Kachel gekoppelt und mit dem Zoom skalierend — NICHT Papier-mm - // (sonst wird die Schraffur bei kleinen Masstäben zur Haarlinie). + // Strichbreite: ein GEWÖHNLICHER Papier-mm-Stift (0.02 mm), wie jede + // andere Linie — nicht mehr an die Musterkachel/Bildschirm-px + // gekoppelt (siehe RPolyline.widthScreen-Kommentar). 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); // Alle Musterläufe eines Polygons teilen EIN z (überlappen einander nicht). const hatchZ = zc++; @@ -389,8 +390,7 @@ export function planToRenderScene(plan: Plan, paperScaleN: number = STAMP_DEFAUL polylines.push({ pts: run.map((v) => [v.x, v.y]), color: hatchCol, - widthMm: hatchPx, - widthScreen: true, + widthMm: hatchMm, z: hatchZ, }); }