fix(snap): correct circle tangent point + no endpoint on closed curves

Two object-snap fixes:

- Tangent on a circle built the snap point as
  `(cx + r·nx, cy, cy + r·ny)` — the y-offset landed in Z and Y was stuck
  at the centre, so the tangent point sat at the wrong place. Build it as
  `(cx + r·nx, cy + r·ny, cz)`.

- Endpoint snapped to a full circle / ellipse: for tessellated curves it
  snaps the first/last point, but a closed curve's first/last is a seam,
  not an endpoint. Skip Endpoint for closed curves — identified by their
  Quadrant snap hints, which arcs never carry — so arcs still snap their
  real endpoints.

Closes #274, closes #275

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-07-05 11:58:44 +03:00
commit e512e57a7c

View file

@ -802,14 +802,25 @@ impl Snapper {
);
}
} else {
// Tessellated curves (Circle, Arc, Ellipse): only arc endpoints.
if let Some(&p) = wire.points.first() {
try_pt(glam::DVec3::new(p[0] as f64, p[1] as f64, p[2] as f64), SnapType::Endpoint);
}
if wire.points.len() > 1 {
if let Some(&p) = wire.points.last() {
// Tessellated curves (Circle, Arc, Ellipse): only an OPEN
// one (an arc) has real endpoints. A full circle / ellipse is
// closed — its tessellation's first/last is a seam point, not
// an endpoint — and is the only tessellated curve that carries
// Quadrant snap hints (arcs never do), so emit no Endpoint for
// those (#275).
let closed = wire
.snap_pts
.iter()
.any(|(_, h)| matches!(h, SnapHint::Quadrant));
if !closed {
if let Some(&p) = wire.points.first() {
try_pt(glam::DVec3::new(p[0] as f64, p[1] as f64, p[2] as f64), SnapType::Endpoint);
}
if wire.points.len() > 1 {
if let Some(&p) = wire.points.last() {
try_pt(glam::DVec3::new(p[0] as f64, p[1] as f64, p[2] as f64), SnapType::Endpoint);
}
}
}
}
}
@ -1101,7 +1112,10 @@ impl Snapper {
} else {
(1.0, 0.0)
};
let w = Vec3::new(cv.x + radius * nx, cv.y, cv.y + radius * ny);
// Circle lies in its own plane at cv.z; the point
// facing the cursor is center + radius·(nx, ny) in XY
// (the y-offset must land in Y, not Z — #274).
let w = Vec3::new(cv.x + radius * nx, cv.y + radius * ny, cv.z);
(w, edge_d * edge_d)
}
};