Fillet and chamfer: refuse an edge pick far from every edge (#820)

Fixes #783.
This commit is contained in:
HiroKenEz authored and GitHub committed 2026-10-13 16:29:33 -04:00
1 parent 53238fba5f
commit 69050c81d9
3 files changed
+56 -1

No files matched your search

+30
View File
@@ -751,9 +751,39 @@ fn edge_points(s: &Session, p: &Value, cmd: &str) -> Result<Vec<Vec3>> {
return Err(bad(cmd, "select 1…1000 edges"));
}
let st = s.model.state();
// A picked point must lie on an edge (within the distance the kernel re-finds an edge from)
// of the body the feature names, or of any body: a pick far from every edge would round
// whichever edge is nearest (#783). Each body's edges are worked out once.
let named = str_(p, "body").filter(|n| st.body(n).is_some());
let mut candidates: Option<Vec<(f64, Vec<solidcraft_kernel::EdgeInfo>)>> = None;
let mut out = Vec::new();
for e in a {
if let Some(pt) = vec3(e).or_else(|| e.get("point").and_then(vec3)) {
let bodies = candidates.get_or_insert_with(|| {
// Mesh bodies have no edges to pick: rounding one is refused later, saying why.
st.bodies
.iter()
.filter(|b| named.is_none_or(|n| b.name == n) && !b.body.is_mesh())
.filter_map(|b| {
let size = b.body.size();
b.body.edges((size * 2e-3).max(1e-3)).ok().map(|edges| (size, edges))
})
.collect()
});
let mut nearest = f64::INFINITY;
let on_edge = bodies.iter().any(|(size, edges)| {
let d = edges
.iter()
.flat_map(|e| e.points.windows(2))
.filter_map(|w| Some(pt.dist_to_segment(*w.first()?, *w.get(1)?)))
.fold(f64::INFINITY, f64::min);
nearest = nearest.min(d);
d <= size * 0.05 + 1e-3
});
if !on_edge && !bodies.is_empty() {
let far = if nearest.is_finite() { format!("the nearest edge is {nearest:.3} mm away") } else { "there is no edge".to_string() };
return Err(bad(cmd, format!("no edge at [{}, {}, {}]: {far}", pt.x, pt.y, pt.z)));
}
out.push(pt);
continue;
}
+25
View File
@@ -33,6 +33,31 @@ fn round_as_tall_as_a_disc_uses_up_its_wall() {
assert!(rel(volume(&mut s), expect) < 2e-4, "{} vs {expect}", volume(&mut s));
}
/// #783: an edge pick far from every edge (beside the body, above it, inside it, or on another
/// body than the one named) is refused and adds nothing, instead of rounding the nearest edge;
/// a pick on an edge, or a little off it as a click lands, still works.
#[test]
fn edge_picks_far_from_every_edge_are_refused() {
let mut s = Session::default();
for (name, x) in [("A", 0), ("B", 40), ("C", 80), ("D", 120)] {
run(&mut s, "solid.box", json!({"length": 20, "width": 20, "height": 10, "corner": [x, 0, 0], "operation": "new", "body_name": name}));
}
run(&mut s, "solid.fillet", json!({"body": "A", "edges": [[10, 0, 10]], "radius": 1}));
run(&mut s, "solid.fillet", json!({"body": "B", "edges": [[50, 0.4, 10.3]], "radius": 1}));
let features = s.doc.features.len();
for (id, p, far) in [
("solid.fillet", json!({"body": "B", "edges": [[50, -15, 10]], "radius": 1}), "15.0"),
("solid.chamfer", json!({"body": "C", "edges": [[90, 10, 40]], "distance": 1}), "31.623 mm"),
("solid.fillet", json!({"edges": [[130, 10, 5]], "radius": 1}), "11.180 mm"),
("solid.fillet", json!({"body": "C", "edges": [[10, 20, 10]], "radius": 1}), "mm away"),
("solid.press_pull", json!({"edges": [[50, -15, 10]], "distance": 1}), "15.0"),
] {
let err = s.execute(id, &p).expect_err(&format!("{id} {p}")).to_string();
assert!(err.contains("no edge at") && err.contains(far), "{id} {p}: {err}");
}
assert_eq!(s.doc.features.len(), features);
}
#[test]
fn fillet_as_wide_as_a_face_rounds_it_off() {
// #199: 14 x 6 x 10 box, the four vertical edges at r = 3 (half the 6 mm end faces).
+1 -1
View File
@@ -5,7 +5,7 @@
{"command": "sketch.line", "params": {"points": [[0, 0], [40, 0], [40, 6], [6, 6], [6, 30], [0, 30]], "closed": true, "ids": ["a", "b", "c", "d", "e", "f"]}},
{"command": "sketch.finish", "params": {}},
{"command": "solid.extrude", "params": {"distance": "width", "direction": "negative", "body_name": "Bracket"}},
{"command": "solid.fillet", "params": {"edges": [[40, 30, 6]], "radius": 2}},
{"command": "solid.fillet", "params": {"edges": [[40, -30, -6]], "radius": 2}},
{"command": "sketch.create", "params": {"plane": "XY", "name": "Holes"}},
{"command": "sketch.circle.center", "params": {"center": [25, 15], "diameter": 8}},
{"command": "sketch.circle.center", "params": {"center": [25, 45], "diameter": 8}},