fix(trim): cut a closed ellipse at the right span

Two faults met on the same command, both of them older than the move to
cadkernel and both hidden until ellipse previews started rendering
truthfully.

The survivor came back in two pieces. `trim_ellipse` handed its cuts to
`trim_intervals`, which is the open-curve routine: it anchors on bounds at
0.0 and 1.0, so a closed ellipse gained invented cuts at the parameter
seam and the surviving arc arrived split either side of it. Worse, a click
in the wrapping piece landed in one of those two halves, so the wrong span
disappeared. A closed ellipse now takes the cyclic route circles already
used — find the cut-to-cut gap holding the click, keep everything else,
and let end < start signal the wrap.

The cut span was also short. That came from `line_ellipse`, fixed upstream
in cadkernel 46e76cc: it returned the geometric angle at the centre rather
than the ellipse parameter, which are the same thing only on a circle. On
anything eccentric the cuts sat tens of degrees off where the line
actually crossed, so the removed piece was consistently too small. The
lock bump brings that fix in.

Elliptical arcs, lines, rays, xlines and open splines keep using
`trim_intervals`; nothing about the open-curve path changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-08-08 15:42:30 +03:00
commit 06a0fc4ddf
2 changed files with 36 additions and 1 deletions

2
Cargo.lock generated
View file

@ -946,7 +946,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cadkernel"
version = "0.1.0"
source = "git+https://github.com/HakanSeven12/cadkernel.git#0e4b23845de75242e00f016feed9fc2341b4a85a"
source = "git+https://github.com/HakanSeven12/cadkernel.git#46e76ccb737a3682ffccf5117e89879ebfd1e227"
[[package]]
name = "calloop"

View file

@ -847,6 +847,41 @@ fn trim_ellipse(orig: &EllipseEnt, ts: &[f64], t_click: f64) -> Vec<EntityType>
let span = t1 - t0;
let angle_at = |t: f64| t0 + span * t;
// A closed ellipse has no ends for `trim_intervals` to anchor on. Handing
// it one makes it invent cuts at the parameter seam, so the survivor comes
// back as two arcs either side of the seam instead of one joined across
// it — and if the click lands in the wrapping piece, the wrong side goes.
// Circles avoid this by treating the cuts cyclically; see `trim_circle`.
if (span - TAU).abs() < 1e-9 {
if ts.len() < 2 {
return vec![];
}
let click = t_click.rem_euclid(1.0);
// The gap holding the click, with the last one wrapping past 1.0 back
// to the first cut.
let gap = (0..ts.len()).find_map(|i| {
let from = ts[i];
let to = if i + 1 < ts.len() {
ts[i + 1]
} else {
ts[0] + 1.0
};
let holds = |t: f64| t >= from - 1e-9 && t <= to + 1e-9;
(holds(click) || holds(click + 1.0)).then_some((from, to))
});
let Some((from, to)) = gap else {
return vec![];
};
// The survivor runs from the far edge of the removed gap all the way
// round to its near edge. Leaving the end below the start is what
// signals the wrap to everything downstream.
let mut e = orig.clone();
e.common.handle = Handle::NULL;
e.start_parameter = angle_at(to % 1.0);
e.end_parameter = angle_at(from % 1.0);
return vec![EntityType::Ellipse(e)];
}
trim_intervals(ts, t_click)
.into_iter()
.filter_map(|(ta, tb)| {