Sketch Fillet: pick the two lines of a corner where more lines meet (#1824) (#1826)

Thanks @SychPL.
This commit is contained in:
Mateusz Sych authored and GitHub committed 2026-10-17 04:07:40 -04:00
1 parent daf1dc4201
commit 0292fa35e6
2 files changed
+76 -9

No files matched your search

@@ -431,3 +431,47 @@ fn ordinary_drawing_keeps_origin_snap_and_real_origin_corners_work() {
assert!(ss.sketch.curves.iter().any(|c| matches!(c.kind, CurveKind::Arc { .. }))); assert!(ss.sketch.curves.iter().any(|c| matches!(c.kind, CurveKind::Arc { .. })));
assert_eq!(ss.profiles.len(), 1); assert_eq!(ss.profiles.len(), 1);
} }
/// #1824: where a third line ends at a corner's point, the point names no single corner; the
/// Fillet window takes the corner's two lines instead and rounds the corner between them.
#[test]
fn fillet_window_rounds_the_corner_between_two_picked_lines() {
let mut h = crate::scenario::Harness::new();
h.app.run("sketch.create", json!({"plane":"XY"})).unwrap();
h.app.run("sketch.line", json!({"points":[[0,0],[10,0]],"ids":["left"]})).unwrap();
h.app.run("sketch.line", json!({"points":["left.end",[20,0]],"ids":["right"]})).unwrap();
h.app.run("sketch.line", json!({"points":["left.end",[18,8]],"ids":["diag"]})).unwrap();
h.settle();
let sid = h.app.session.active_sketch.unwrap();
h.step(&json!({"start":"sketch.fillet"})).unwrap();
// The shared point ends three lines, so it names no single corner: pick the two lines.
h.step(&json!({"click":{"world":[16,0,0]}})).unwrap();
h.step(&json!({"click":{"world":[13,3,0]}})).unwrap();
assert_eq!(dialog_curves(&h), vec!["right".to_string(), "diag".to_string()], "{:?}", h.app.status);
h.step(&json!({"widget":"OK"})).unwrap();
assert!(h.app.dialog.is_none(), "{:?}", h.app.status);
let sk = h.app.session.model.state().sketch(sid).unwrap().sketch.clone();
assert_eq!(sk.curves.iter().filter(|c| matches!(c.kind, CurveKind::Arc { .. })).count(), 1, "{:?}", h.app.status);
}
/// #1824 (review): the Chamfer window takes a corner's two lines the same way.
#[test]
fn chamfer_window_cuts_the_corner_between_two_picked_lines() {
let mut h = crate::scenario::Harness::new();
h.app.run("sketch.create", json!({"plane":"XY"})).unwrap();
h.app.run("sketch.line", json!({"points":[[0,0],[10,0]],"ids":["left"]})).unwrap();
h.app.run("sketch.line", json!({"points":["left.end",[20,0]],"ids":["right"]})).unwrap();
h.app.run("sketch.line", json!({"points":["left.end",[18,8]],"ids":["diag"]})).unwrap();
h.settle();
let sid = h.app.session.active_sketch.unwrap();
let before = h.app.session.model.state().sketch(sid).unwrap().sketch.curves.len();
h.step(&json!({"start":"sketch.chamfer.equal_distance"})).unwrap();
h.step(&json!({"click":{"world":[16,0,0]}})).unwrap();
h.step(&json!({"click":{"world":[13,3,0]}})).unwrap();
assert_eq!(dialog_curves(&h), vec!["right".to_string(), "diag".to_string()], "{:?}", h.app.status);
h.step(&json!({"widget":"OK"})).unwrap();
assert!(h.app.dialog.is_none(), "{:?}", h.app.status);
let sk = h.app.session.model.state().sketch(sid).unwrap().sketch.clone();
assert_eq!(sk.curves.len(), before + 1, "a chamfer line: {:?}", h.app.status);
assert!(sk.curve_index("left").is_some(), "the third line stays");
}
+32 -9
View File
@@ -150,9 +150,10 @@ fn inputs(k: &Mod) -> Vec<SelInput> {
// The centre is required: `sketch.pattern.circular` has no default for it. // The centre is required: `sketch.pattern.circular` has no default for it.
Mod::PatternCirc { .. } => vec![SelInput::new("Entities", CURVES | SKETCH_POINTS, true), SelInput::new("Center", SKETCH_POINTS, false)], Mod::PatternCirc { .. } => vec![SelInput::new("Entities", CURVES | SKETCH_POINTS, true), SelInput::new("Center", SKETCH_POINTS, false)],
// A fillet rounds any number of corners to one radius (`points`). Each chamfer command // A fillet rounds any number of corners to one radius (`points`). Each chamfer command
// cuts one corner, so its input holds one. // cuts one corner: its point, or its two lines. Two lines name the corner between them
Mod::Corner { chamfer: false, .. } => vec![SelInput::new("Corners", SKETCH_POINTS, true)], // where more lines meet at its point (#1824), for both.
Mod::Corner { .. } => vec![SelInput::new("Corner", SKETCH_POINTS, false)], Mod::Corner { chamfer: false, .. } => vec![SelInput::new("Corners", SKETCH_POINTS | CURVES, true)],
Mod::Corner { .. } => vec![SelInput::new("Corner", SKETCH_POINTS | CURVES, true)],
} }
} }
@@ -548,6 +549,17 @@ fn length(app: &SolveApp, label: &str, text: &str) -> Result<f64, String> {
} }
Ok(v) Ok(v)
} }
/// The curve ids picked in input `i` (a corner's two lines, #1824).
fn line_ids(inputs: &[SelInput], i: usize) -> Vec<String> {
items(inputs, i)
.iter()
.filter_map(|x| match x {
Sel::SketchCurve { id } => Some(id.clone()),
_ => None,
})
.collect()
}
/// Corner point ids within the active sketch, including qualified script selections. /// Corner point ids within the active sketch, including qualified script selections.
fn corner_ids(app: &SolveApp, inputs: &[SelInput], i: usize) -> Vec<String> { fn corner_ids(app: &SolveApp, inputs: &[SelInput], i: usize) -> Vec<String> {
items(inputs, i).iter().filter_map(|sel| point_id(&app.session, sel).map(str::to_string)).collect() items(inputs, i).iter().filter_map(|sel| point_id(&app.session, sel).map(str::to_string)).collect()
@@ -734,15 +746,26 @@ pub fn commands(app: &SolveApp, k: &Mod, inputs: &[SelInput], extra: &Map<String
} }
Mod::Corner { chamfer: false, size, .. } => { Mod::Corner { chamfer: false, size, .. } => {
let corners = corner_ids(app, inputs, 0); let corners = corner_ids(app, inputs, 0);
if corners.is_empty() { let lines = line_ids(inputs, 0);
return Err("select the corners to round first".into());
}
// The radius is checked where it is evaluated, against the document's parameters. // The radius is checked where it is evaluated, against the document's parameters.
("sketch.fillet", json!({"points": corners, "radius": size.trim()})) match (corners.is_empty(), lines.as_slice()) {
(true, []) => return Err("select the corners to round first".into()),
(false, []) => ("sketch.fillet", json!({"points": corners, "radius": size.trim()})),
// Two lines name their corner even where other lines end at its point (#1824).
(true, [a, b]) => ("sketch.fillet", json!({"a": a, "b": b, "radius": size.trim()})),
_ => return Err("select corner points, or the two lines of one corner".into()),
}
} }
Mod::Corner { ctype, size, distance2, angle, flip, .. } => { Mod::Corner { ctype, size, distance2, angle, flip, .. } => {
let point = corner_ids(app, inputs, 0).into_iter().next().ok_or("select the corner to cut first")?; let flipped = *flip && *ctype > 0;
let mut p = corner_ref(app, &point, *flip && *ctype > 0)?; let mut p = match (corner_ids(app, inputs, 0).as_slice(), line_ids(inputs, 0).as_slice()) {
([point], []) => corner_ref(app, point, flipped)?,
// Flip swaps the lines, so `distance` applies to the other leg.
([], [a, b]) if flipped => json!({"a": b, "b": a}),
([], [a, b]) => json!({"a": a, "b": b}),
([], []) => return Err("select the corner to cut first".into()),
_ => return Err("select one corner point, or the two lines of one corner".into()),
};
p["distance"] = json!(corner_size(app, size_label(true, *ctype), size, ValueKind::Length)?); p["distance"] = json!(corner_size(app, size_label(true, *ctype), size, ValueKind::Length)?);
match ctype { match ctype {
1 => { 1 => {