From b8f9727a27dd2ed4b4286785b0d8a6ce2ee86dfb Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 7 Aug 2026 11:22:04 +0300 Subject: [PATCH] fix(grip): stop double-offsetting the UCS grip snap marker The extension anchors needed the same pane-to-canvas lift the snap marker already got, but the block that added it was placed next to the one that was already there rather than merged into it, so `screen` took the tile origin twice. A single full-canvas tile has no origin to add, so it only shows once the viewport is split. Fold the anchors into the existing block. Also require the ray to pass through the snapped point before offering it as a distance reference. Only four acquisitions are kept and the oldest is dropped as new ones arrive, so the ray that produced a snap can be gone by the time a distance is typed; picking the nearest survivor unconditionally sent the point off along an unrelated ray instead of falling back to plain point input. `extension_snap` projects onto the ray in exact f64, so the tolerance only has to clear rounding, scaled with the coordinate to hold at UTM magnitudes. Co-Authored-By: Claude Opus 5 --- src/app/update/viewport.rs | 9 ++------- src/snap.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index b62a496ad..ab37cd726 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -2043,24 +2043,19 @@ impl OpenCADStudio { let world = snap_hit.map(|s| s.world).unwrap_or(raw); self.tabs[i].snap_result = snap_hit; if let Some(s) = self.tabs[i].snap_result.as_mut() { + // Snap marker and its extension anchors are pane-local; lift them to + // absolute canvas px. s.screen.x += tile_b.x; s.screen.y += tile_b.y; - if let Some(base) = s.extension_base.as_mut() { base.x += tile_b.x; base.y += tile_b.y; } - if let Some(base) = s.extension_base2.as_mut() { base.x += tile_b.x; base.y += tile_b.y; } } - if let Some(s) = self.tabs[i].snap_result.as_mut() { - // Snap marker is pane-local; lift it to absolute canvas px. - s.screen.x += tile_b.x; - s.screen.y += tile_b.y; - } use acadrust::types::Vector3; let v3 = |d: glam::DVec3| Vector3::new(d.x, d.y, d.z); diff --git a/src/snap.rs b/src/snap.rs index 9636dd505..8cfb2c96a 100644 --- a/src/snap.rs +++ b/src/snap.rs @@ -1657,6 +1657,23 @@ impl Snapper { let unit_dir = glam::DVec3::new(dir.x / len, dir.y / len, 0.0); + // The ray has to actually pass through the snapped point. Only + // four acquisitions are kept and the oldest is dropped as new + // ones come in, so by the time a typed distance arrives the ray + // that produced this snap may be gone — without this the nearest + // surviving ray wins by default and the distance flies off along + // it. Failing here instead leaves the caller on plain point input. + // + // `extension_snap` builds the point by exact f64 projection onto + // this same ray, so a match sits on it to rounding: the tolerance + // only has to clear f64 noise, and scaling it with the coordinate + // keeps that true at UTM magnitudes. (`extension_bases_screen` + // answers the same question in screen pixels because it also has + // to catch a *crossing* base, which is a looser fit than this.) + let tol = 1e-9_f64.max(2e-12 * origin.x.abs().max(origin.y.abs())); + if err2 > tol * tol { + continue; + } if best.as_ref().map_or(true, |(best_err, _, _)| err2 < *best_err) { best = Some((err2, origin, unit_dir)); }