fix(linetype): keep complex linetypes precise at large coordinates

A circle with a complex linetype (embedded shapes/text, e.g. IZOLACE) drew
its dashes and symbols scattered far off the entity at UTM-scale
coordinates. Two defects compounded, both catastrophic-cancellation at a
~1.2M-unit origin:

- The single-entity complex-linetype path fed apply_along the wire's HIGH
  double-single half only (`base.points`), an absolute f32 quantised to
  ~0.1 at 1.2M. Walking the pattern along those points blew the shape /
  dash positions up to ~1e308. Reconstruct the absolute f64 path from
  high+low, re-origin it at the first vertex, walk apply_along in that
  local frame, then lift each result wire back to world with
  shift_wire_to_world (now pub(crate)) — the same local-frame trick the
  MLINE complex-linetype path already uses. Fixes every entity, not just
  circles.

- The circle was tessellated with truck's circle_arc (arc-through-three-
  points), whose fit cancels catastrophically at 1.2M and returned a ~3%
  radius wobble with uneven segments. Tessellate directly as a cos/sin
  polyline (adaptive segment count from the curve tolerance); adding a
  small ±r term to the centre stays precise, and the Lines path RTE-splits
  it. Arc and polyline bulges share the circle_arc root and remain to fix.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-23 17:16:57 +03:00
1 parent 2d3f33de10
commit 5e2b67c642
3 files changed
+70 -23

No files matched your search

+40 -1
View File
@@ -1227,9 +1227,47 @@ pub(crate) fn tessellate_entity(
// this path.
if let Some(clt) = crate::io::linetypes::resolve_complex_lt(document, lt_name) {
if let Some(base) = bases.first() {
// Walk the dash / glyph layout in a local frame so `apply_along`'s
// f32 math stays precise, then lift each wire back to world DS.
// `base.points` is the double-single HIGH half; at UTM coordinates
// (−1.2M) that alone quantises to ~0.1, which jitters the complex
// linetype's shapes and dashes. Reconstruct the absolute f64 path
// (high + low) and re-origin it at the first vertex before walking.
let abs = |i: usize| -> [f64; 3] {
let p = base.points[i];
let l = base.points_low.get(i).copied().unwrap_or([0.0; 3]);
[
p[0] as f64 + l[0] as f64,
p[1] as f64 + l[1] as f64,
p[2] as f64 + l[2] as f64,
]
};
let origin = base
.points
.iter()
.position(|p| !p[0].is_nan())
.map(&abs)
.unwrap_or([0.0; 3]);
let local: Vec<[f32; 3]> = base
.points
.iter()
.enumerate()
.map(|(i, p)| {
if p[0].is_nan() {
[f32::NAN; 3]
} else {
let a = abs(i);
[
(a[0] - origin[0]) as f32,
(a[1] - origin[1]) as f32,
(a[2] - origin[2]) as f32,
]
}
})
.collect();
let mut wires = text::complex_lt::apply_along(
&base.name,
&base.points,
&local,
&clt,
(lt_scale * pslt_factor).max(1e-4),
entity_color,
@@ -1241,6 +1279,7 @@ pub(crate) fn tessellate_entity(
);
if !wires.is_empty() {
for w in &mut wires {
convert::tessellate::shift_wire_to_world(w, origin);
set_wire_aabb(w, aabb);
}
return wires;
+1 -1
View File
@@ -74,7 +74,7 @@ pub(crate) fn points_to_ds(
/// The MLINE complex-linetype path uses this because `apply_along` lays out its
/// dashes and glyphs in f32, which would otherwise quantise fine spacing far
/// from the origin.
fn shift_wire_to_world(w: &mut WireModel, origin: [f64; 3]) {
pub(crate) fn shift_wire_to_world(w: &mut WireModel, origin: [f64; 3]) {
if !w.points.is_empty() {
let mut hi = Vec::with_capacity(w.points.len());
let mut lo = Vec::with_capacity(w.points.len());