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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5 committed 2026-08-07 11:22:04 +03:00
1 parent 687bc2079d
commit b8f9727a27
2 files changed
+19 -7

No files matched your search

+2 -7
View File
@@ -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);
+17
View File
@@ -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));
}