Emboss: say how far the picked point is from a face (#392) (#1733)

Thanks @SychPL.
This commit is contained in:
Mateusz Sych authored and GitHub committed 2026-10-16 10:26:20 -04:00
1 parent 77b33efd8f
commit 3501fa2c54
2 files changed
+41 -6

No files matched your search

+24 -6
View File
@@ -161,6 +161,8 @@ fn emboss(s: &mut Session, p: &Value) -> Result<Value> {
let at = solidcraft_doc::apply_point(&s.active_component_frame(), local);
let state = s.world_state();
let mut candidates = Vec::new();
// The nearest face off the point: (distance, body), for the error when none holds it.
let mut nearest: Option<(f64, &str)> = None;
for body in &state.bodies {
let definition = body.definition_name();
if !targets.is_empty() && !targets.iter().any(|n| n == definition || n == &body.name) {
@@ -179,14 +181,30 @@ fn emboss(s: &mut Session, p: &Value) -> Result<Value> {
inside.then_some(((at - a).dot(n).abs(), *face as usize))
})
.min_by(|a, b| a.0.total_cmp(&b.0));
if let Some((distance, index)) = closest
&& distance < (body.body.size() * 1e-3).max(1e-3)
{
candidates.push((body, index));
match closest {
Some((distance, index)) if distance < (body.body.size() * 1e-3).max(1e-3) => candidates.push((body, index)),
Some((distance, _)) if nearest.is_none_or(|(d, _)| distance < d) => nearest = Some((distance, body.name.as_str())),
_ => {}
}
}
if candidates.len() != 1 {
return Err(bad(cmd, "`face` must identify exactly one target body surface"));
// #392: say what is wrong with the point, not only that it picks no single face.
let point = format!("[{}, {}, {}]", local.x, local.y, local.z);
match (candidates.as_slice(), nearest) {
([], Some((d, body))) => {
return Err(bad(
cmd,
format!("`face` {point} lies on no face: the nearest, on `{body}`, is {d:.3} mm away; give a point on the face"),
));
}
([], None) => return Err(bad(cmd, format!("`face` {point} lies on no face of the target bodies"))),
([_], _) => {}
(more, _) => {
let names: Vec<&str> = more.iter().map(|(b, _)| b.name.as_str()).collect();
return Err(bad(
cmd,
format!("`face` {point} lies on faces of {} bodies ({}): name one in `targets`", names.len(), names.join(", ")),
));
}
}
let (selected, index) = candidates.first().copied().ok_or_else(|| bad(cmd, "no target face"))?;
let name = selected.definition_name();
@@ -92,6 +92,23 @@ fn emboss_and_deboss_on_a_face() {
assert!(s.execute("solid.emboss", &json!({"sketch": "Logo", "depth": 1, "mode": "sideways"})).is_err());
}
#[test]
fn emboss_face_off_the_part_says_how_far() {
// #392: a point beside the face was refused with "`face` must identify exactly one target
// body surface", which didn't say the point was off the face.
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 10, "body_name": "A"}));
run(&mut s, "sketch.create", json!({"plane": {"face": [20, 15, 10]}, "name": "Logo"}));
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [5, 5], "p1": [15, 15]}));
run(&mut s, "sketch.finish", json!({}));
let e = s.execute("solid.emboss", &json!({"sketch": "Logo", "depth": 1, "face": [20, 15, 11.25]})).unwrap_err().to_string();
assert!(e.contains("[20, 15, 11.25] lies on no face: the nearest, on `A`, is 1.250 mm away"), "{e}");
run(&mut s, "solid.box", json!({"length": 10, "width": 30, "height": 10, "corner": [40, 0, 0], "operation": "new_body", "body_name": "B"}));
let e = s.execute("solid.emboss", &json!({"sketch": "Logo", "depth": 1, "face": [40, 15, 5]})).unwrap_err().to_string();
assert!(e.contains("lies on faces of 2 bodies (A, B): name one in `targets`"), "{e}");
run(&mut s, "solid.emboss", json!({"sketch": "Logo", "depth": 1, "face": [20, 15, 10]}));
}
fn cylinder_badge() -> Session {
let mut s = Session::default();
run(&mut s, "parameters.add", json!({"name":"badge_depth","expression":"1 mm"}));