diff --git a/crates/engine/src/cmd/sketch_modify.rs b/crates/engine/src/cmd/sketch_modify.rs index 56d2a850..2b56d1a5 100644 --- a/crates/engine/src/cmd/sketch_modify.rs +++ b/crates/engine/src/cmd/sketch_modify.rs @@ -488,9 +488,13 @@ fn corner(sk: &Sketch, p: &Value, cmd: &str) -> Result<(usize, usize, usize, usi let (la, lb) = if let Some(r) = str_(p, "point") { let q = sk.resolve_point(r).ok_or_else(|| bad(cmd, format!("unknown point `{r}`")))?; let lines: Vec = (0..sk.curves.len()).filter(|i| ends(*i).is_some_and(|(a, b)| a == q || b == q) && !sk.is_linked_curve(*i)).collect(); - match lines[..] { + // A construction line ending there too (a diagonal that centres something) is not one of + // the corner's sides: it keeps its end on the virtual sharp corner (#1026). + let sides: Vec = + if lines.len() == 2 { lines } else { lines.into_iter().filter(|i| sk.curves.get(*i).is_some_and(|c| !c.construction)).collect() }; + match sides[..] { [a, b] => (a, b), - _ => return Err(bad(cmd, "the corner must join exactly two lines")), + _ => return Err(bad(cmd, "the corner must join exactly two lines (or give the two lines as `a` and `b`)")), } } else { let a = str_(p, "a").and_then(|i| sk.curve_index(i)).ok_or_else(|| bad(cmd, "`a` must be a line"))?; diff --git a/crates/engine/src/cmd/sketch_modify_tests.rs b/crates/engine/src/cmd/sketch_modify_tests.rs index 1e2277a2..75b70e08 100644 --- a/crates/engine/src/cmd/sketch_modify_tests.rs +++ b/crates/engine/src/cmd/sketch_modify_tests.rs @@ -678,6 +678,34 @@ fn ellipse_radii_dimensions() { assert!((a - std::f64::consts::PI * 60.0).abs() < 1e-6, "{a}"); } +/// #1026: a construction diagonal ending at a rectangle's corner is not one of the corner's +/// sides. The corner is rounded, and the diagonal keeps its end on the virtual sharp corner. +#[test] +fn fillet_a_corner_a_construction_diagonal_ends_in() { + let mut s = new_sketch(); + run(&mut s, "sketch.line", json!({"points": [[0, 0], [50, 0], [50, 30], [0, 30]], "closed": true, "ids": ["a", "b", "c", "d"]})); + let diag = ids(&run(&mut s, "sketch.line", json!({"points": ["c.end", "a.end"], "construction": true}))["curves"])[0].clone(); + let f = run(&mut s, "sketch.fillet", json!({"point": "a.end", "radius": 2})); + let arc = ids(&f["curves"])[0].clone(); + let sk = sketch(&s); + let ai = sk.curve_index(&arc).unwrap(); + assert!((sk.radius(ai).unwrap() - 2.0).abs() < 1e-7); + let c = sk.center(ai).unwrap(); + assert!(c.dist(Vec2::new(48.0, 2.0)) < 1e-6, "{c:?}"); + // The diagonal still runs from (0, 30) to the sharp corner (50, 0). + let di = sk.curve_index(&diag).unwrap(); + let Some(CurveKind::Line { a, b }) = sk.curves.get(di).map(|x| &x.kind) else { panic!("the diagonal is a line") }; + let (pa, pb) = (sk.point(*a).unwrap(), sk.point(*b).unwrap()); + assert!(pa.dist(Vec2::new(0.0, 30.0)) < 1e-6 && pb.dist(Vec2::new(50.0, 0.0)) < 1e-6, "{pa:?} {pb:?}"); + assert!(sk.curves.get(di).unwrap().construction); + // Three lines at a corner, none of them construction, stay ambiguous. + let mut s = new_sketch(); + run(&mut s, "sketch.line", json!({"points": [[0, 0], [50, 0], [50, 30]], "ids": ["a", "b"]})); + run(&mut s, "sketch.line", json!({"points": ["a.end", [80, 20]]})); + let e = s.execute("sketch.fillet", &json!({"point": "a.end", "radius": 2})).unwrap_err().to_string(); + assert!(e.contains("exactly two lines") && e.contains("`a` and `b`"), "{e}"); +} + #[test] fn fillet_a_line_and_an_arc() { let mut s = new_sketch();