feat(mline): GPU-dash multiline elements with shared A-type alignment

MLINE dashed elements were fully CPU-expanded (apply_along) — one wire per
dash — to keep the parallel lines in phase. Move pure-dash elements onto the
GPU dash shader and end-align every element to the multiline's centre-line so
they share one interior phase.

- wire_distances now accumulates arc-length from the double-single (high+low)
  points, so the dash phase stays precise at UTM coordinates. f32-high-only
  deltas quantised ~0.1 there and drifted the parallel lines apart — the
  original reason MLINE dashes were CPU-side.
- New WireModel.dash_align_end carries a shared begin/end dash length. The
  shader uses it as align_end for every parallel element (the interior phase
  depends on align_end, not the wire's own length) while align_total stays the
  element's own length, so each still ends on a dash at its own endpoint.
- MLINE tessellation derives that length once from the centre-line, routes
  pure dash/space/dot elements to the GPU pattern and text-bearing elements to
  apply_along.
- apply_along gained an "A"-type mode (shared reference length): a solid
  begin/end dash and a phased interior, so its dashes — and embedded text —
  line up with the GPU-dashed sibling elements instead of tiling from start.

Pure-dash MLINEs now cost one WireModel + pattern per element instead of N
segments; single (non-MLINE) entities keep the from-start tiling unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-17 02:59:11 +03:00
1 parent 743dc1026d
commit 5bd683cf4e
41 files changed
+263 -14

No files matched your search

+2
View File
@@ -187,6 +187,7 @@ impl CadCommand for AlignedDimensionCommand {
let p2 = p2.as_vec3();
Some(WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimaligned_preview".into(),
points: vec![[p1.x, p1.y, p1.z], [p2.x, p2.y, p2.z]],
@@ -235,6 +236,7 @@ fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel {
let d2 = d2.as_vec3();
WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimaligned_preview".into(),
points: vec![
+1
View File
@@ -131,6 +131,7 @@ fn v3(pt: DVec3) -> Vector3 {
fn preview_wire(points: Vec<Vec3>) -> WireModel {
WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimangular_preview".to_string(),
points: points.into_iter().map(|p| [p.x, p.y, p.z]).collect(),
+1
View File
@@ -181,6 +181,7 @@ fn v3(p: DVec3) -> Vector3 {
fn preview_line(a: Vec3, b: Vec3) -> WireModel {
WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimdia_preview".into(),
points: vec![[a.x, a.y, a.z], [b.x, b.y, b.z]],
+1
View File
@@ -180,6 +180,7 @@ impl CadCommand for DimBaselineCommand {
let pt = pt.as_vec3();
Some(WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimbase_preview".into(),
points: vec![
+1
View File
@@ -160,6 +160,7 @@ impl CadCommand for DimContinueCommand {
let dim_line_pt2 = pt + perp * (dim_line_perp - pt.dot(perp));
Some(WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimcont_preview".into(),
points: vec![
+1
View File
@@ -86,6 +86,7 @@ impl CadCommand for DimJogLineCommand {
let d = 0.3_f32;
Some(WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimjog_preview".into(),
// Jog zigzag in the XY drawing plane (Z is elevation, ~0). The old
+1
View File
@@ -101,6 +101,7 @@ impl CadCommand for DimTeditCommand {
let d = 0.2_f32;
Some(WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimtedit_preview".into(),
// Marker box in the XY drawing plane (Z is elevation, ~0). The old
+1
View File
@@ -164,6 +164,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
}
WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "leader_preview".into(),
points,
+1
View File
@@ -219,6 +219,7 @@ fn v3(pt: DVec3) -> Vector3 {
fn preview_wire(points: Vec<DVec3>) -> WireModel {
WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimlinear_preview".to_string(),
points: points
+1
View File
@@ -151,6 +151,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
}
WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "mleader_preview".into(),
points,
+1
View File
@@ -459,6 +459,7 @@ impl CadCommand for MLeaderCollectCommand {
fn preview_wire(pts: &[Vec3]) -> WireModel {
WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "mleader_edit_preview".into(),
points: pts.iter().map(|p| [p.x, p.y, p.z]).collect(),
+1
View File
@@ -122,6 +122,7 @@ fn ordinate_elbow(feature: DVec3, leader: DVec3, is_x: bool) -> DVec3 {
fn preview_wire(points: Vec<Vec3>) -> WireModel {
WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimordinate_preview".into(),
points: points.into_iter().map(|p| [p.x, p.y, p.z]).collect(),
+1
View File
@@ -91,6 +91,7 @@ impl CadCommand for QdimCommand {
let d = 0.5_f32;
Some(WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "qdim_preview".into(),
points: vec![[pt.x - d * 3.0, pt.y, pt.z], [pt.x + d * 3.0, pt.y, pt.z]],
+1
View File
@@ -186,6 +186,7 @@ fn v3(pt: DVec3) -> Vector3 {
fn preview_wire(points: Vec<Vec3>) -> WireModel {
WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "dimradius_preview".to_string(),
points: points.into_iter().map(|p| [p.x, p.y, p.z]).collect(),
+1
View File
@@ -140,6 +140,7 @@ impl CadCommand for TableCommand {
let z = pt.z;
Some(WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "table_preview".into(),
points: vec![
+1
View File
@@ -85,6 +85,7 @@ impl CadCommand for ToleranceCommand {
let d = 0.15_f32;
Some(WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "tolerance_preview".into(),
points: vec![