Interference: a pin a hair off the axis of its hole gets its crescent (#1893)
Thanks @NOWN-DEV.
This commit is contained in:
1 parent
713111d9df
commit
962070e955
4 files changed
+466
-2
No files matched your search
@@ -73,7 +73,7 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
),
|
||||
CommandSpec::new("joint.solve", "Solve Joints", solve_cmd).icon("compute").params("re-place the occurrences from the joints → conflicts, errors, dof"),
|
||||
CommandSpec::new("inspect.interference", "Interference", interference).at("SOLID", "INSPECT").icon("interference").noundo().params(
|
||||
"bodies?: [names] (default: all, in world placement) → interferences: pairs that overlap with the overlap volume, area, bounding box and centre; unchecked: pairs that could not be checked, with the reason. Bodies are pruned by bounding box first, so any number of bodies is fine; at most 20000 pairs whose boxes overlap are intersected per call (truncated: true and skipped_pairs: n say when more were left out)",
|
||||
"bodies?: [names] (default: all, in world placement) → interferences: pairs that overlap with the overlap volume, area, bounding box and centre (a pin a hair off the axis of a hole of its size also has near_coaxial: {offset_mm, length_mm}, its volume the crescent the walls share); unchecked: pairs that could not be checked, with the reason. Bodies are pruned by bounding box first, so any number of bodies is fine; at most 20000 pairs whose boxes overlap are intersected per call (truncated: true and skipped_pairs: n say when more were left out)",
|
||||
),
|
||||
];
|
||||
|
||||
@@ -1156,6 +1156,16 @@ fn interference(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (mut hits, mut unchecked) = (Vec::new(), Vec::new());
|
||||
for (i, j) in pairs {
|
||||
let (Some(a), Some(b)) = (bodies.get(i), bodies.get(j)) else { continue };
|
||||
let threshold = 1e-9 * a.body.size().max(1.0).powi(3);
|
||||
// A pin a hair off the axis of its hole: the crescent the walls share, which the
|
||||
// boolean can't intersect.
|
||||
if let Some(o) = solidcraft_kernel::coaxial_overlap(&a.body, &b.body) {
|
||||
if o.volume > threshold {
|
||||
hits.push(json!({"a": a.name, "b": b.name, "volume_mm3": o.volume, "area_mm2": o.area, "center": o.centroid,
|
||||
"bbox": {"min": o.bbox.min, "max": o.bbox.max}, "near_coaxial": {"offset_mm": o.offset, "length_mm": o.length}}));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// A pair whose shared part can't be worked out is reported, not passed as clear.
|
||||
let (shared, report) =
|
||||
solidcraft_kernel::capture_boolean_diagnostics(|| solidcraft_kernel::boolean(&a.body, &b.body, solidcraft_kernel::BoolOp::Intersect));
|
||||
@@ -1178,7 +1188,7 @@ fn interference(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if m.volume > 1e-9 * a.body.size().max(1.0).powi(3) {
|
||||
if m.volume > threshold {
|
||||
hits.push(json!({"a": a.name, "b": b.name, "volume_mm3": m.volume, "area_mm2": m.area, "center": m.centroid, "bbox": {"min": m.bbox.min, "max": m.bbox.max}}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1452,3 +1452,31 @@ mod cam;
|
||||
// Plots and interference of motion studies, on the four-bar and slider-crank above.
|
||||
#[path = "motion_analysis_tests.rs"]
|
||||
mod motion_analysis;
|
||||
|
||||
/// A pin through a hole of its own radius whose axis is a hair off the hole's (parts placed from
|
||||
/// rounded coordinates): the walls are all but tangent along two lines, which the boolean can't
|
||||
/// intersect. Each offset gets a checked answer: clear on the axis and within the kernel's
|
||||
/// same-surface tolerance, and otherwise the crescent the pin shares with the plate, against
|
||||
/// the textbook lens of two circles. The pin is chamfered, so it is not a plain prism.
|
||||
#[test]
|
||||
fn interference_of_a_pin_a_hair_off_its_hole() {
|
||||
let crescent = |r: f64, e: f64| std::f64::consts::PI * r * r - (2.0 * r * r * (e / (2.0 * r)).acos() - e / 2.0 * (4.0 * r * r - e * e).sqrt());
|
||||
for offset in [0.0, 1e-6, 5.9e-4, 1e-2] {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.box", json!({"length": 20, "width": 20, "height": 5, "corner": [-10, -10, 0], "body_name": "Plate"}));
|
||||
run(&mut s, "solid.cylinder", json!({"radius": 3, "height": 9, "base": [0, 0, -2], "operation": "cut", "targets": ["Plate"]}));
|
||||
run(&mut s, "solid.cylinder", json!({"radius": 3, "height": 9, "base": [offset, 0, -2], "operation": "new", "body_name": "Pin"}));
|
||||
run(&mut s, "solid.chamfer", json!({"edges": [[offset + 3.0, 0, 7]], "distance": 0.5, "body": "Pin"}));
|
||||
let i = run(&mut s, "inspect.interference", json!({"bodies": ["Plate", "Pin"]}));
|
||||
assert_eq!(i["unchecked"].as_array().map(Vec::len), Some(0), "{offset}: {i}");
|
||||
if offset < 1e-5 {
|
||||
assert_eq!(i["count"], 0, "{offset}: {i}");
|
||||
continue;
|
||||
}
|
||||
assert_eq!(i["count"], 1, "{offset}: {i}");
|
||||
let want = 5.0 * crescent(3.0, offset);
|
||||
let got = i["interferences"][0]["volume_mm3"].as_f64().unwrap_or(0.0);
|
||||
assert!((got - want).abs() < 1e-6 * want, "{offset}: {got} against {want}: {i}");
|
||||
assert!((i["interferences"][0]["near_coaxial"]["offset_mm"].as_f64().unwrap_or(0.0) - offset).abs() < 1e-9, "{i}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user