2D-Plan: Strichmuster (Umriss/Linie/Bogen) im nativen wgpu-Renderer zeichnen
Papier-mm-Strichmuster (dash) wurden bisher komplett ignoriert und immer durchgezogen gerendert — im nativen 2D-Fenster gab es dafür bislang gar keinen Mechanismus. `split_dash` portiert den bereits für Schraffuren bewährten `applyDashRuns`-Algorithmus (glPlanHatch.ts) nach Rust und zerlegt einen Linienzug in seine "an"-Teilstücke; die Phase läuft dabei über bereits verkettete Zeichnungszüge UND über die neu adaptiv tessellierten Bögen hinweg durch, sodass z. B. der gestrichelte Türschwenk-Bogen gleichmäßig gestrichelt bleibt statt an jedem Segment neu anzusetzen.
This commit is contained in:
@@ -67,6 +67,120 @@ pub(crate) fn tessellate_arc(center: Point, from: Point, to: Point, r: f32, px_p
|
|||||||
pts
|
pts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Faktor Papier-mm -> Modell-Meter fuer ALLGEMEINE Linien-/Bogen-Striche, bei
|
||||||
|
/// der Referenz-Skala `paper_scale_n = 100` des Renderers (siehe `gpu::Renderer`
|
||||||
|
/// und `toRenderScene.ts`s `MM_TO_M`): `N/1000`. Bewusst NICHT die Hatch-
|
||||||
|
/// spezifische `DASH_MM_TO_M`-Konstante aus `glPlanHatch.ts` (die ist an die
|
||||||
|
/// Musterkachel gekoppelt, nicht an den Papier-Massstab).
|
||||||
|
const DASH_MM_TO_M: f32 = 100.0 / 1000.0;
|
||||||
|
|
||||||
|
/// Portiert `applyDashRuns` (`src/plan/glPlan/glPlanHatch.ts`) auf Modell-Meter-
|
||||||
|
/// Punktzuege: zerlegt EINEN zusammenhaengenden Lauf entlang eines Strichmusters
|
||||||
|
/// (Papier-mm, `DASH_MM_TO_M`-Referenz) in seine durchgezogenen Teilstuecke. Die
|
||||||
|
/// Phase laeuft ENTLANG DES GESAMTEN LAUFS durch (ueber Stuetzpunkt-Ecken
|
||||||
|
/// hinweg) — wichtig fuer bereits verkettete Umriss-/Zeichnungszuege UND fuer
|
||||||
|
/// die neu adaptiv tessellierten Boegen. Ungerade Musterlaenge wird verdoppelt
|
||||||
|
/// (Standard-Dash-Semantik). Leeres/ungueltiges Muster oder <2 Punkte -> EIN
|
||||||
|
/// Stueck (unveraendert, durchgezogen).
|
||||||
|
fn split_dash(pts: &[Point], dash: &[f32]) -> Vec<Vec<Point>> {
|
||||||
|
let pat: Vec<f32> = dash
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(|d| d.max(0.0) * DASH_MM_TO_M)
|
||||||
|
.filter(|d| *d > 0.0)
|
||||||
|
.collect();
|
||||||
|
if pat.is_empty() || pts.len() < 2 {
|
||||||
|
return vec![pts.to_vec()];
|
||||||
|
}
|
||||||
|
let cycle: Vec<f32> = if pat.len() % 2 == 0 {
|
||||||
|
pat.clone()
|
||||||
|
} else {
|
||||||
|
[pat.as_slice(), pat.as_slice()].concat()
|
||||||
|
};
|
||||||
|
let total: f32 = cycle.iter().sum();
|
||||||
|
if total <= 1e-9 {
|
||||||
|
return vec![pts.to_vec()];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out: Vec<Vec<Point>> = Vec::new();
|
||||||
|
let mut idx = 0usize;
|
||||||
|
let mut dash_left = cycle[0];
|
||||||
|
let mut on = true;
|
||||||
|
let mut piece: Option<Vec<Point>> = Some(vec![pts[0]]);
|
||||||
|
|
||||||
|
for w in pts.windows(2) {
|
||||||
|
let mut a = w[0];
|
||||||
|
let b = w[1];
|
||||||
|
let mut seg_len = ((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt();
|
||||||
|
if seg_len < 1e-12 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ux = (b[0] - a[0]) / seg_len;
|
||||||
|
let uy = (b[1] - a[1]) / seg_len;
|
||||||
|
while seg_len > 1e-9 {
|
||||||
|
let step = seg_len.min(dash_left);
|
||||||
|
let mid = [a[0] + ux * step, a[1] + uy * step];
|
||||||
|
if on {
|
||||||
|
if piece.is_none() {
|
||||||
|
piece = Some(vec![a]);
|
||||||
|
}
|
||||||
|
piece.as_mut().unwrap().push(mid);
|
||||||
|
}
|
||||||
|
a = mid;
|
||||||
|
seg_len -= step;
|
||||||
|
dash_left -= step;
|
||||||
|
if dash_left <= 1e-9 {
|
||||||
|
if on {
|
||||||
|
if let Some(p) = piece.take() {
|
||||||
|
if p.len() >= 2 {
|
||||||
|
out.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
on = !on;
|
||||||
|
idx += 1;
|
||||||
|
dash_left = cycle[idx % cycle.len()];
|
||||||
|
if on {
|
||||||
|
piece = Some(vec![a]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(p) = piece.take() {
|
||||||
|
if p.len() >= 2 {
|
||||||
|
out.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Zeichnet einen Linienzug entweder als EINEN gehrten Streifen (kein/leeres
|
||||||
|
/// Muster) oder — bei gesetztem `dash` — als seine durchgezogenen Teilstuecke
|
||||||
|
/// (`split_dash`), je einzeln offen gestrichen. Vereinfachung: ein gestrichelter
|
||||||
|
/// GESCHLOSSENER Umriss (`closed=true`) wird trotzdem als offene Teilstuecke
|
||||||
|
/// gezeichnet — der Ring-Schluss braucht bei einer Dash-Luecke ohnehin keine
|
||||||
|
/// durchgehende Gehrung.
|
||||||
|
fn stroke_dashed_or_solid(
|
||||||
|
geo: &mut GpuGeometry,
|
||||||
|
pts: &[Point],
|
||||||
|
closed: bool,
|
||||||
|
color: Rgba,
|
||||||
|
stroke_mm: f32,
|
||||||
|
dash: Option<&[f32]>,
|
||||||
|
bounds: &mut Bounds,
|
||||||
|
) {
|
||||||
|
match dash {
|
||||||
|
Some(d) if !d.is_empty() => {
|
||||||
|
for piece in split_dash(pts, d) {
|
||||||
|
if piece.len() >= 2 {
|
||||||
|
geo.stroke_polyline(&piece, false, color, stroke_mm, bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => geo.stroke_polyline(pts, closed, color, stroke_mm, bounds),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Signierte Flaeche (Shoelace); >0 = CCW (Modell-Y nach oben).
|
/// Signierte Flaeche (Shoelace); >0 = CCW (Modell-Y nach oben).
|
||||||
fn signed_area(pts: &[Point]) -> f32 {
|
fn signed_area(pts: &[Point]) -> f32 {
|
||||||
let mut a = 0.0f32;
|
let mut a = 0.0f32;
|
||||||
@@ -427,12 +541,11 @@ pub fn compile_scene_scaled(scene: &Scene, px_per_m: f32) -> GpuGeometry {
|
|||||||
for poly in &scene.fills {
|
for poly in &scene.fills {
|
||||||
compile_fill(&mut geo, poly, &mut bounds);
|
compile_fill(&mut geo, poly, &mut bounds);
|
||||||
}
|
}
|
||||||
// 2) Umrisse (geschlossene Ringe als EIN gehrter Streifen, inkl. Schluss-Kante).
|
// 2) Umrisse (geschlossene Ringe als EIN gehrter Streifen, inkl. Schluss-Kante
|
||||||
// `dash` existiert bereits im Typ (siehe `types::Outline`), wird aber erst
|
// — ausser bei Dash, siehe `stroke_dashed_or_solid`).
|
||||||
// mit der Dash-Zerlegung (Welle 2) beruecksichtigt.
|
|
||||||
for o in &scene.outlines {
|
for o in &scene.outlines {
|
||||||
if o.width_mm > 0.0 && o.pts.len() >= 2 {
|
if o.width_mm > 0.0 && o.pts.len() >= 2 {
|
||||||
geo.stroke_polyline(&o.pts, true, o.color, o.width_mm, &mut bounds);
|
stroke_dashed_or_solid(&mut geo, &o.pts, true, o.color, o.width_mm, o.dash.as_deref(), &mut bounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 3) Offene Polylinien (verbundene Umrisskanten / 2D-Zeichnungszuege) als EIN
|
// 3) Offene Polylinien (verbundene Umrisskanten / 2D-Zeichnungszuege) als EIN
|
||||||
@@ -440,12 +553,11 @@ pub fn compile_scene_scaled(scene: &Scene, px_per_m: f32) -> GpuGeometry {
|
|||||||
// Stumpfkappen (closed=false).
|
// Stumpfkappen (closed=false).
|
||||||
for pl in &scene.polylines {
|
for pl in &scene.polylines {
|
||||||
if pl.width_mm > 0.0 && pl.pts.len() >= 2 {
|
if pl.width_mm > 0.0 && pl.pts.len() >= 2 {
|
||||||
geo.stroke_polyline(&pl.pts, false, pl.color, pl.width_mm, &mut bounds);
|
stroke_dashed_or_solid(&mut geo, &pl.pts, false, pl.color, pl.width_mm, pl.dash.as_deref(), &mut bounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 4) Kreisboegen: zoomabhaengig in eine Punktfolge zerlegt (`tessellate_arc`),
|
// 4) Kreisboegen: zoomabhaengig in eine Punktfolge zerlegt (`tessellate_arc`),
|
||||||
// dann wie eine offene Polylinie gestrichen. `dash` wird (noch) ignoriert
|
// dann wie eine offene Polylinie gestrichen (mit oder ohne Dash).
|
||||||
// (durchgezogen gezeichnet) — die Zerlegung in Teilstriche folgt in Welle 2.
|
|
||||||
for a in &scene.arcs {
|
for a in &scene.arcs {
|
||||||
compile_arc(&mut geo, a, px_per_m, &mut bounds);
|
compile_arc(&mut geo, a, px_per_m, &mut bounds);
|
||||||
}
|
}
|
||||||
@@ -482,18 +594,18 @@ fn compile_arc(geo: &mut GpuGeometry, a: &Arc, px_per_m: f32, bounds: &mut Bound
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let pts = tessellate_arc(a.center, a.from, a.to, a.r, px_per_m);
|
let pts = tessellate_arc(a.center, a.from, a.to, a.r, px_per_m);
|
||||||
geo.stroke_polyline(&pts, false, a.color, a.width_mm, bounds);
|
stroke_dashed_or_solid(geo, &pts, false, a.color, a.width_mm, a.dash.as_deref(), bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_line(geo: &mut GpuGeometry, l: &Line, bounds: &mut Bounds) {
|
fn compile_line(geo: &mut GpuGeometry, l: &Line, bounds: &mut Bounds) {
|
||||||
let w = if l.width_mm > 0.0 { l.width_mm } else { 0.18 };
|
let w = if l.width_mm > 0.0 { l.width_mm } else { 0.18 };
|
||||||
geo.stroke_polyline(&[l.a, l.b], false, l.color, w, bounds);
|
stroke_dashed_or_solid(geo, &[l.a, l.b], false, l.color, w, l.dash.as_deref(), bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::types::Arc;
|
use crate::types::{Arc, Outline};
|
||||||
|
|
||||||
// --- tessellate_arc: adaptive Bogen-Zerlegung ----------------------------
|
// --- tessellate_arc: adaptive Bogen-Zerlegung ----------------------------
|
||||||
|
|
||||||
@@ -535,6 +647,34 @@ mod tests {
|
|||||||
assert!(fine.len() - 1 <= 512, "max. 512 Segmente erwartet");
|
assert!(fine.len() - 1 <= 512, "max. 512 Segmente erwartet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- split_dash: geometrische Strichmuster-Zerlegung ---------------------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_dash_gerade_linie_erwartete_stueckzahl_und_laenge() {
|
||||||
|
let pts = [[0.0, 0.0], [10.0, 0.0]];
|
||||||
|
// 10mm/10mm bei Referenz-Skala (DASH_MM_TO_M=0.1) -> 1m/1m Zyklus,
|
||||||
|
// 10m Gesamtlaenge -> 5 "an"-Stuecke a 1m (50% Tastverhaeltnis).
|
||||||
|
let pieces = split_dash(&pts, &[10.0, 10.0]);
|
||||||
|
assert_eq!(pieces.len(), 5, "5 an-Stuecke erwartet");
|
||||||
|
let total: f32 = pieces
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
p.windows(2)
|
||||||
|
.map(|w| ((w[1][0] - w[0][0]).powi(2) + (w[1][1] - w[0][1]).powi(2)).sqrt())
|
||||||
|
.sum::<f32>()
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
assert!((total - 5.0).abs() < 1e-3, "Gesamtlaenge der an-Stuecke ~5m, war {total}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_dash_leeres_muster_unveraendert() {
|
||||||
|
let pts = [[0.0, 0.0], [3.0, 4.0], [10.0, 4.0]];
|
||||||
|
let pieces = split_dash(&pts, &[]);
|
||||||
|
assert_eq!(pieces.len(), 1);
|
||||||
|
assert_eq!(pieces[0], pts.to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
// --- compile_scene_scaled: End-zu-End-Verdrahtung -------------------------
|
// --- compile_scene_scaled: End-zu-End-Verdrahtung -------------------------
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -555,4 +695,79 @@ mod tests {
|
|||||||
assert!(!geo.line_verts.is_empty(), "Bogen sollte Linien-Vertices erzeugen");
|
assert!(!geo.line_verts.is_empty(), "Bogen sollte Linien-Vertices erzeugen");
|
||||||
assert!(!geo.line_idx.is_empty(), "Bogen sollte Linien-Indizes erzeugen");
|
assert!(!geo.line_idx.is_empty(), "Bogen sollte Linien-Indizes erzeugen");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dash_reduziert_indexanzahl_gegenueber_durchgezogenem_umriss() {
|
||||||
|
let ring = vec![[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]];
|
||||||
|
let solid = Scene {
|
||||||
|
outlines: vec![Outline {
|
||||||
|
pts: ring.clone(),
|
||||||
|
color: [0.0, 0.0, 0.0, 1.0],
|
||||||
|
width_mm: 0.2,
|
||||||
|
dash: None,
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let dashed = Scene {
|
||||||
|
outlines: vec![Outline {
|
||||||
|
pts: ring,
|
||||||
|
color: [0.0, 0.0, 0.0, 1.0],
|
||||||
|
width_mm: 0.2,
|
||||||
|
dash: Some(vec![10.0, 10.0]),
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let geo_solid = compile_scene_scaled(&solid, PX_PER_M);
|
||||||
|
let geo_dashed = compile_scene_scaled(&dashed, PX_PER_M);
|
||||||
|
assert!(!geo_solid.line_idx.is_empty());
|
||||||
|
assert!(!geo_dashed.line_idx.is_empty());
|
||||||
|
assert_ne!(
|
||||||
|
geo_solid.line_idx.len(),
|
||||||
|
geo_dashed.line_idx.len(),
|
||||||
|
"gestrichelter Umriss sollte eine andere (geringere) Indexanzahl als der durchgezogene ergeben"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gesamtlaenge eines offenen Punktzugs (Modell-Meter).
|
||||||
|
fn polyline_len(pts: &[Point]) -> f32 {
|
||||||
|
pts.windows(2)
|
||||||
|
.map(|w| ((w[1][0] - w[0][0]).powi(2) + (w[1][1] - w[0][1]).powi(2)).sqrt())
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gestrichelter_tuerschwenk_bogen_kein_panic_und_kuerzere_deckung() {
|
||||||
|
// Spiegelt den echten Anwendungsfall (Tuerschwenk-Boegen, dash 0.06/0.04mm).
|
||||||
|
let center = [0.0, 0.0];
|
||||||
|
let from = [1.0, 0.0];
|
||||||
|
let to = [0.0, 1.0];
|
||||||
|
let dash = vec![0.06, 0.04];
|
||||||
|
let scene = Scene {
|
||||||
|
arcs: vec![Arc {
|
||||||
|
center,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
r: 1.0,
|
||||||
|
color: [0.1, 0.1, 0.1, 1.0],
|
||||||
|
width_mm: 0.05,
|
||||||
|
dash: Some(dash.clone()),
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
// Rundlauf durch die volle Pipeline: darf nicht paniken, muss Geometrie liefern.
|
||||||
|
let geo = compile_scene_scaled(&scene, PX_PER_M);
|
||||||
|
assert!(!geo.line_idx.is_empty(), "gestrichelter Bogen sollte trotzdem Geometrie erzeugen");
|
||||||
|
|
||||||
|
// Geometrische Invariante an den Bausteinen selbst: die Summe der "an"-
|
||||||
|
// Stuecke deckt WENIGER Bogenlaenge ab als der volle (durchgezogene) Bogen.
|
||||||
|
let arc_pts = tessellate_arc(center, from, to, 1.0, PX_PER_M);
|
||||||
|
let full_len = polyline_len(&arc_pts);
|
||||||
|
let pieces = split_dash(&arc_pts, &dash);
|
||||||
|
assert!(pieces.len() > 1, "sollte in mehrere an-Stuecke gesplittet werden");
|
||||||
|
let on_len: f32 = pieces.iter().map(|p| polyline_len(p)).sum();
|
||||||
|
assert!(
|
||||||
|
on_len < full_len,
|
||||||
|
"gestrichelte Deckung ({on_len}) sollte kuerzer als der volle Bogen ({full_len}) sein"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,18 @@
|
|||||||
// einen GPU-Glyphen-Atlas (glyphon/cosmic-text), keine Vektor-Ersatzschrift.
|
// einen GPU-Glyphen-Atlas (glyphon/cosmic-text), keine Vektor-Ersatzschrift.
|
||||||
//
|
//
|
||||||
// Das erzeugte Objekt matcht 1:1 die serde-Structs render2d::types::Scene
|
// Das erzeugte Objekt matcht 1:1 die serde-Structs render2d::types::Scene
|
||||||
// { fills:[{pts,color}], outlines:[{pts,color,widthMm}], polylines:[{pts,color,widthMm}],
|
// { fills:[{pts,color}], outlines:[{pts,color,widthMm,dash}],
|
||||||
// arcs:[{center,from,to,r,color,widthMm,dash}], lines:[{a,b,color,widthMm}],
|
// polylines:[{pts,color,widthMm,dash}], arcs:[{center,from,to,r,color,widthMm,dash}],
|
||||||
// texts:[{pos,content,sizeMm,color,align}] }
|
// lines:[{a,b,color,widthMm,dash}], texts:[{pos,content,sizeMm,color,align}] }
|
||||||
// mit Point = [x,y] (Meter) und Rgba = [r,g,b,a] (0..1).
|
// mit Point = [x,y] (Meter) und Rgba = [r,g,b,a] (0..1).
|
||||||
//
|
//
|
||||||
// Bögen werden NICHT hier tessellliert (anders als früher): der Rust-Renderer
|
// Bögen werden NICHT hier tessellliert (anders als früher): der Rust-Renderer
|
||||||
// zerlegt sie zoomabhängig (`tessellate::tessellate_arc`), damit sie bei jeder
|
// zerlegt sie zoomabhängig (`tessellate::tessellate_arc`), damit sie bei jeder
|
||||||
// Vergrößerung glatt bleiben statt sichtbare Facetten zu zeigen.
|
// Vergrößerung glatt bleiben statt sichtbare Facetten zu zeigen. Strichmuster
|
||||||
|
// (`dash`, mm Papier) werden ebenfalls unverändert durchgereicht — das geometrische
|
||||||
|
// Zerschneiden in Teilstücke (`applyDashRuns`-Algorithmus) passiert erst NACH der
|
||||||
|
// Bogen-Tessellierung in Rust (`tessellate::split_dash`), damit die Phase über die
|
||||||
|
// gesamte (ggf. verkettete oder adaptiv tessellierte) Strecke durchläuft.
|
||||||
|
|
||||||
import type { Plan, Primitive } from "./generatePlan";
|
import type { Plan, Primitive } from "./generatePlan";
|
||||||
import { applyDashRuns, buildHatchRuns } from "./glPlan/glPlanHatch";
|
import { applyDashRuns, buildHatchRuns } from "./glPlan/glPlanHatch";
|
||||||
@@ -254,10 +258,11 @@ export function planToRenderScene(plan: Plan): RScene {
|
|||||||
if (curPts && curSrc && curPts.length >= 2) {
|
if (curPts && curSrc && curPts.length >= 2) {
|
||||||
const col = toRgba(curSrc.color ?? DEFAULT_LINE, 1) ?? [0.1, 0.1, 0.1, 1];
|
const col = toRgba(curSrc.color ?? DEFAULT_LINE, 1) ?? [0.1, 0.1, 0.1, 1];
|
||||||
const closed = curPts.length > 2 && samePt(curPts[0], curPts[curPts.length - 1]);
|
const closed = curPts.length > 2 && samePt(curPts[0], curPts[curPts.length - 1]);
|
||||||
|
const dash = curSrc.dash ?? null;
|
||||||
if (closed) {
|
if (closed) {
|
||||||
outlines.push({ pts: curPts.slice(0, -1), color: col, widthMm: curSrc.weightMm });
|
outlines.push({ pts: curPts.slice(0, -1), color: col, widthMm: curSrc.weightMm, dash });
|
||||||
} else {
|
} else {
|
||||||
polylines.push({ pts: curPts, color: col, widthMm: curSrc.weightMm });
|
polylines.push({ pts: curPts, color: col, widthMm: curSrc.weightMm, dash });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
curPts = null;
|
curPts = null;
|
||||||
@@ -318,7 +323,7 @@ export function planToRenderScene(plan: Plan): RScene {
|
|||||||
// Genuin einzelnes Segment (Türblatt, Referenzlinie, Symbol) → Stumpfkappen ok.
|
// Genuin einzelnes Segment (Türblatt, Referenzlinie, Symbol) → Stumpfkappen ok.
|
||||||
flushRun();
|
flushRun();
|
||||||
const col = toRgba(p.color ?? DEFAULT_LINE, 1) ?? [0.1, 0.1, 0.1, 1];
|
const col = toRgba(p.color ?? DEFAULT_LINE, 1) ?? [0.1, 0.1, 0.1, 1];
|
||||||
lines.push({ a: [p.a.x, p.a.y], b: [p.b.x, p.b.y], color: col, widthMm: p.weightMm });
|
lines.push({ a: [p.a.x, p.a.y], b: [p.b.x, p.b.y], color: col, widthMm: p.weightMm, dash: p.dash ?? null });
|
||||||
}
|
}
|
||||||
} else if (p.kind === "arc") {
|
} else if (p.kind === "arc") {
|
||||||
flushRun();
|
flushRun();
|
||||||
|
|||||||
Reference in New Issue
Block a user