mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
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:
@@ -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, ¶meter);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user