mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
* Hole: a hole moved off the material says it removes no material (#10701) A hole whose position, parameter or sketch point moves it off the part cut nothing and said nothing: the feature had no warning, and a parameter sweep listed the value as fine. A hole whose tool is clear of every body it cuts now warns "the hole removes no material" ("1 of 2 holes remove no material" for several), as a pattern does of its copies. The check compares boxes only, from the meshes the hole already found its face on, so a large hole pattern costs no extra booleans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hole: a hole wider than the part says it removes all of it (#10702) A through hole wider than the part removed the whole body with no message, also when a parameter widened it later. The hole now warns "the hole removes all of Body1" when a body it cuts is gone after the cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6672,6 +6672,35 @@ fn path_at(segs: &[kernel::PathSeg], d: f64) -> (Vec3, Vec3) {
|
||||
}
|
||||
|
||||
/// Whether a copy has no material to cut in any of its eligible targets.
|
||||
/// The bodies a hole cuts: those its feature names, else its component's and its own (as
|
||||
/// `apply_traced` picks a cut's targets).
|
||||
fn hole_targets<'a>(st: &'a ModelState, f: &Feature) -> Vec<&'a ModelBody> {
|
||||
let all = f.participants.len() == 1 && f.participants.first().is_some_and(|t| t == "*");
|
||||
st.bodies
|
||||
.iter()
|
||||
.filter(|b| all || if f.participants.is_empty() { b.component == f.component || b.feature == f.id } else { f.participants.contains(&b.name) })
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether hole tool `tool` is clear of every body the hole cuts, by their boxes (#10701). Boxes
|
||||
/// only: a probe boolean for every hole of a large hole pattern would double its work, and the
|
||||
/// bodies' meshes are there already (the hole found its face on them). A body of another
|
||||
/// component is in another frame: the hole is then not known to miss.
|
||||
fn hole_misses(st: &ModelState, f: &Feature, tool: &Body) -> bool {
|
||||
let targets = hole_targets(st, f);
|
||||
let bounds = tool.outline_bounds();
|
||||
if targets.is_empty()
|
||||
|| targets.iter().any(|b| b.component != f.component)
|
||||
|| bounds.is_empty()
|
||||
|| !bounds.min.is_finite()
|
||||
|| !bounds.max.is_finite()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let tool_box = Some(bounds.inflated(1e-6));
|
||||
targets.iter().all(|b| more::boxes_apart(&tool_box, &more::mesh_box(b)))
|
||||
}
|
||||
|
||||
fn copy_misses(st: &ModelState, f: &Feature, targets: &[String], tools: &[Traced]) -> bool {
|
||||
let targets = if targets.is_empty() { &f.participants } else { targets };
|
||||
let all = targets.len() == 1 && targets.first().is_some_and(|t| t == "*");
|
||||
@@ -7598,7 +7627,23 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
let handled = sheet_hole_cut(st, f, &tools, warning)?;
|
||||
let tools: Vec<Traced> = tools.into_iter().map(|(tool, _)| tool).collect();
|
||||
if handled.is_empty() {
|
||||
// A hole moved off the material (a changed position, parameter or sketch point)
|
||||
// cuts nothing: it says so, as a pattern does of its copies (#10701).
|
||||
let missed = tools.iter().filter(|tool| hole_misses(st, f, &tool.body)).count();
|
||||
let total = tools.len();
|
||||
let targets: Vec<String> = hole_targets(st, f).iter().map(|b| b.name.clone()).collect();
|
||||
apply_traced(doc, st, f, tools, Operation::Cut, &[])?;
|
||||
match (missed, total) {
|
||||
(0, _) => {}
|
||||
(_, 1) => append_warning(warning, "the hole removes no material".into()),
|
||||
_ => append_warning(warning, format!("{missed} of {total} holes remove no material")),
|
||||
}
|
||||
// A hole wider than the part takes all of it away (#10702): almost never what a
|
||||
// hole means, so it says which bodies are gone.
|
||||
let gone: Vec<&str> = targets.iter().map(String::as_str).filter(|n| st.body(n).is_none()).collect();
|
||||
if !gone.is_empty() {
|
||||
append_warning(warning, format!("the hole removes all of {}", gone.join(", ")));
|
||||
}
|
||||
} else {
|
||||
apply_beside_sheet_cut(doc, st, f, tools, Operation::Cut, &[], &handled)?;
|
||||
}
|
||||
|
||||
@@ -450,3 +450,47 @@ fn a_hole_may_start_over_the_mouth_of_an_existing_bore() {
|
||||
let e = s.execute("solid.hole", &json!({"position": [-1, 15, 20], "diameter": 8})).unwrap_err().to_string();
|
||||
assert!(e.contains("is not on a face of a body"), "{e}");
|
||||
}
|
||||
|
||||
/// A hole moved off the part cuts nothing and says so (#10701), as a pattern's copies do: by a
|
||||
/// parameter of its position or a sketch point moved away. Back on the part it is silent.
|
||||
#[test]
|
||||
fn a_hole_moved_off_the_part_says_it_removes_no_material() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "parameters.add", json!({"name": "hx", "expression": "10 mm"}));
|
||||
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 10, "name": "Plate"}));
|
||||
run(&mut s, "solid.hole", json!({"position": ["hx", "10 mm", "10 mm"], "diameter": 5, "name": "H1"}));
|
||||
assert_eq!(said(&s, "H1"), "");
|
||||
change(&mut s, "hx", "100 mm");
|
||||
assert!((volume(&mut s) - 12_000.0).abs() < 1e-6, "nothing is cut");
|
||||
assert!(said(&s, "H1").contains("the hole removes no material"), "{}", said(&s, "H1"));
|
||||
change(&mut s, "hx", "30 mm");
|
||||
assert_eq!(said(&s, "H1"), "");
|
||||
// Holes at two sketch points, one of them moved off the plate.
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "offset": 10, "name": "HP"}));
|
||||
run(&mut s, "sketch.point", json!({"point": [10, 20], "id": "a"}));
|
||||
run(&mut s, "sketch.point", json!({"point": [20, 20], "id": "b"}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.hole", json!({"sketch": "HP", "points": ["a", "b"], "diameter": 4, "name": "H2"}));
|
||||
assert_eq!(said(&s, "H2"), "");
|
||||
run(&mut s, "sketch.edit", json!({"sketch": "HP"}));
|
||||
run(&mut s, "sketch.move_point", json!({"point": "a", "to": [100, 100]}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
assert!(said(&s, "H2").contains("1 of 2 holes remove no material"), "{}", said(&s, "H2"));
|
||||
}
|
||||
|
||||
/// A hole wider than the part takes all of it away and says so (#10702): at creation, and when
|
||||
/// a parameter widens it later. A hole across the part's width keeps the rest, silently.
|
||||
#[test]
|
||||
fn a_hole_wider_than_the_part_says_it_removes_all_of_it() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "parameters.add", json!({"name": "hd", "expression": "40 mm"}));
|
||||
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 10, "name": "Plate"}));
|
||||
run(&mut s, "solid.hole", json!({"position": [10, 10, 10], "diameter": "hd", "name": "H1"}));
|
||||
assert_eq!(said(&s, "H1"), "");
|
||||
assert_eq!(s.model.state().bodies.len(), 1);
|
||||
change(&mut s, "hd", "100 mm");
|
||||
assert!(s.model.state().bodies.is_empty());
|
||||
assert!(said(&s, "H1").contains("the hole removes all of Body1"), "{}", said(&s, "H1"));
|
||||
change(&mut s, "hd", "5 mm");
|
||||
assert_eq!(said(&s, "H1"), "");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user