Sketch Break: a length becomes the distance between the old ends; no piece of no length (#6826) (#6834)

Breaking a dimensioned line left the length on the first piece, which the solver stretched
back to the whole length, skewing the line. A cut on (or a hair from) an end, as where a tangent
arc meets a line, made a zero-length piece with its coincident and collinear ties every time
Break was clicked there.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
dudedagger42
2026-11-02 14:03:42 +00:00
committed by GitHub
parent abea79d8ab
commit 1d98b59883
2 changed files with 76 additions and 0 deletions
+20
View File
@@ -769,6 +769,9 @@ fn trim_path(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({"trimmed": trimmed, "removed": removed, "skipped": skipped, "sketch": info}))
}
/// Cuts closer than this to a curve's end or to each other are one place (mm).
const BREAK_GAP: f64 = 1e-6;
fn break_cmd(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "sketch.break";
let points = if p.get("points").is_some() { Some(vec2_list(cmd, p, "points", 1000)?) } else { None };
@@ -804,6 +807,23 @@ fn break_cmd(s: &mut Session, p: &Value) -> Result<Value> {
if ts.is_empty() || (!sh.is_full() && ts.iter().any(|t| *t <= t0 || *t >= t1)) {
return Err(bad(cmd, "nothing to break at there"));
}
// A cut on (or a hair from) an end or another cut would make a piece of no length, and
// its ties: where a curve ends, or touches a tangent arc, it is already broken.
let ends = if sh.is_full() { Vec::new() } else { vec![sh.point(t0), sh.point(t1)] };
let mut kept: Vec<(f64, Option<usize>)> = Vec::new();
for (t, c) in ts.iter().zip(&cutters) {
let q = sh.point(*t);
if !ends.iter().chain(kept.iter().map(|k| sh.point(k.0)).collect::<Vec<_>>().iter()).any(|e| e.dist(q) < BREAK_GAP) {
kept.push((*t, *c));
}
}
if kept.is_empty() {
return Err(bad(cmd, "the curve is already broken there"));
}
let (ts, cutters): (Vec<f64>, Vec<Option<usize>>) = kept.into_iter().unzip();
// Its length (and its midpoint, an equal length) measured the whole curve: left on the
// first piece, it would stretch that piece back to the old size and skew the sketch.
release_extent(sk, ci);
let (pieces, pts) = split_at(sk, ci, &ts)?;
for (pi, c) in pts.iter().zip(&cutters) {
if let Some(c) = c {
@@ -4718,3 +4718,59 @@ fn trim_keeps_offset_distances_driving_after_trims_drag_and_reopen() {
verify(&reopened, 3.0, &parameter);
}
}
/// Breaking a piece again where it was already broken adds nothing: no zero-length piece and
/// no new constraints, however often it is clicked.
#[test]
fn breaking_again_where_a_curve_was_broken_adds_nothing() {
let mut s = new_sketch();
let a = ids(&run(&mut s, "sketch.line", json!({"points": [[0, 0], [20, 0]]}))["curves"])[0].clone();
// A circle tangent to A (as the slot's end arc is to its side): a tangent touch is a double
// root, found as two cuts a hair apart.
let x = 20.0 / 3.0 + 0.123_456_789;
run(&mut s, "sketch.circle.center", json!({"center": [x, 3.0], "radius": 3.0}));
run(&mut s, "sketch.dimension", json!({"entities": [a.clone()], "value": 20}));
run(&mut s, "sketch.break", json!({"curve": a, "at": [5, 0]}));
let count = |s: &Session| {
let k = sketch(s);
(k.curves.len(), k.constraints.len())
};
let first = count(&s);
let shown = |s: &Session| {
let k = sketch(s);
k.curves
.iter()
.map(|c| match c.kind {
CurveKind::Line { a, b } => format!("line {:?}-{:?}", k.point(a), k.point(b)),
_ => "other".into(),
})
.collect::<Vec<_>>()
};
eprintln!("after the first break: {:?}", shown(&s));
for _ in 0..5 {
let k = sketch(&s);
let left = k
.curves
.iter()
.find(|c| {
matches!(c.kind, CurveKind::Line { .. })
&& k.shape(k.curve_index(&c.id).unwrap()).is_some_and(|sh| sh.project(Vec2::new(5.0, 0.0)).dist(Vec2::new(5.0, 0.0)) < 1e-9)
})
.map(|c| c.id.clone());
let Some(left) = left else { panic!("no piece through (5, 0): {:?}", shown(&s)) };
let _ = s.execute("sketch.break", &json!({"curve": left, "at": [5, 0]}));
let _ = s.execute("sketch.break", &json!({"curve": left, "at": [x - 1e-7, 0]}));
}
let k = sketch(&s);
eprintln!("first {first:?} now {:?}; constraints {:?}", count(&s), k.constraints.iter().map(|c| format!("{:?}", c.kind)).collect::<Vec<_>>());
assert_eq!(count(&s), first, "repeated breaks added curves or constraints");
// The line stays where it was, 20 long: the length became the distance between its old ends.
let k = sketch(&s);
for c in &k.curves {
if let CurveKind::Line { a, b } = c.kind {
let (pa, pb) = (k.point(a).unwrap(), k.point(b).unwrap());
assert!(pa.y.abs() < 1e-9 && pb.y.abs() < 1e-9 && pa.dist(pb) > 1e-3, "{:?}", shown(&s));
}
}
assert!(k.constraints.iter().any(|c| matches!(c.kind, ConstraintKind::Distance { value, .. } if close(value, 20.0))), "{:?}", k.constraints);
}