Extrude and Revolve To Object on curved faces follow the face: the tool runs through the target body and is cut back to its surface (the piece that starts at the profile); tests against a cylinder and a sphere (volumes by integration) and a revolve ending on a post; Shell Outside/Both on a cylinder through the exact curved offsets
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
1e515547d8
commit
1f619e49cc
3 files changed
+196
-3
No files matched your search
@@ -0,0 +1,106 @@
|
||||
//! To Object extents on curved targets, and shells outside/both on curved bodies.
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::Session;
|
||||
|
||||
fn run(s: &mut Session, id: &str, p: Value) -> Value {
|
||||
match s.execute(id, &p) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("{id} {p}: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn body(s: &mut Session, b: &str) -> Value {
|
||||
run(s, "inspect.measure", json!({ "bodies": [b] }))["bodies"][0].clone()
|
||||
}
|
||||
|
||||
/// A square post extruded up to a horizontal cylinder (axis along x, r 10, centre z 30): its
|
||||
/// top takes the cylinder's shape, so the post is shortest in the middle.
|
||||
#[test]
|
||||
fn extrude_to_a_cylinder_follows_it() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.cylinder", json!({"base": [-20, 0, 30], "axis": [1, 0, 0], "radius": 10, "height": 40, "body_name": "Bar"}));
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "P"}));
|
||||
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [-4, -4], "p1": [4, 4]}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "P", "to": [0, 0, 20], "body_name": "Post"}));
|
||||
let b = body(&mut s, "Post");
|
||||
// Highest at the post's sides (y = ±4): z = 30 − √(100 − 16); the volume follows the arc.
|
||||
let top = b["bbox"]["max"][2].as_f64().unwrap_or(0.0);
|
||||
assert!((top - (30.0 - (100.0f64 - 16.0).sqrt())).abs() < 1e-3, "{b}");
|
||||
// ∫ over y of (30 − √(100 − y²)) × 8 (x width), y from −4 to 4.
|
||||
let n = 2000;
|
||||
let want: f64 = (0..n).map(|i| -4.0 + 8.0 * (i as f64 + 0.5) / n as f64).map(|y| (30.0 - (100.0 - y * y).sqrt()) * 8.0 * 8.0 / n as f64).sum();
|
||||
assert!((b["volume_mm3"].as_f64().unwrap_or(0.0) - want).abs() < want * 2e-3, "{} vs {want}", b["volume_mm3"]);
|
||||
}
|
||||
|
||||
/// A round post extruded up to a sphere (r 15 at z 40): it stops on the sphere's underside.
|
||||
#[test]
|
||||
fn extrude_to_a_sphere_follows_it() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.sphere", json!({"center": [0, 0, 40], "radius": 15, "body_name": "Ball"}));
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "P"}));
|
||||
run(&mut s, "sketch.circle.center", json!({"center": [0, 0], "radius": 5}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "P", "to": [0, 0, 25], "body_name": "Post"}));
|
||||
let b = body(&mut s, "Post");
|
||||
let top = b["bbox"]["max"][2].as_f64().unwrap_or(0.0);
|
||||
assert!((top - (40.0 - (225.0f64 - 25.0).sqrt())).abs() < 1e-2, "{b}");
|
||||
// Volume: cylinder to z 25 plus the ring between the sphere cap and the plane z = 25.
|
||||
let cap = |r: f64| 40.0 - (225.0 - r * r).sqrt();
|
||||
let n = 2000;
|
||||
let want: f64 = (0..n).map(|i| 5.0 * (i as f64 + 0.5) / n as f64).map(|r| cap(r) * 2.0 * PI * r * 5.0 / n as f64).sum();
|
||||
assert!((b["volume_mm3"].as_f64().unwrap_or(0.0) - want).abs() < want * 2e-3, "{} vs {want}", b["volume_mm3"]);
|
||||
}
|
||||
|
||||
/// Shell Outside and Both on a cylinder (open top): exact offsets of the curved wall.
|
||||
#[test]
|
||||
fn shell_outside_and_both_on_a_cylinder() {
|
||||
let shell = |dir: &str| {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.cylinder", json!({"radius": 10, "height": 20, "body_name": "C"}));
|
||||
run(&mut s, "solid.shell", json!({"faces": [[0, 0, 20]], "thickness": 2, "direction": dir}));
|
||||
body(&mut s, "C")["volume_mm3"].as_f64().unwrap_or(0.0)
|
||||
};
|
||||
let out = PI * (144.0 * 22.0 - 100.0 * 20.0);
|
||||
assert!((shell("outside") - out).abs() < out * 1e-3, "{} vs {out}", shell("outside"));
|
||||
let both = PI * (121.0 * 21.0 - 81.0 * 19.0);
|
||||
assert!((shell("both") - both).abs() < both * 1e-3, "{} vs {both}", shell("both"));
|
||||
}
|
||||
|
||||
/// A ring section revolved To Object up to a post standing in its path: it ends on the post's
|
||||
/// round side (touching, not overlapping), between first contact and the post's centre.
|
||||
#[test]
|
||||
fn revolve_to_a_cylinder_follows_it() {
|
||||
for side in [1.0, -1.0] {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.cylinder", json!({"base": [0, 14.0 * side, -5], "radius": 4, "height": 15, "body_name": "Post"}));
|
||||
run(&mut s, "sketch.create", json!({"plane": "XZ", "name": "S"}));
|
||||
run(&mut s, "sketch.line", json!({"points": [[10, 0], [15, 0], [15, 5], [10, 5]], "closed": true}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
let r = s.execute("solid.revolve", &json!({"sketch": "S", "axis": "y", "to": [4, 14.0 * side, 2], "body_name": "Arc"}));
|
||||
if r.is_err() {
|
||||
continue; // the post on the side the revolve turns away from is past the turn
|
||||
}
|
||||
let st = s.model.state();
|
||||
let (arc, post) = (st.body("Arc").unwrap(), st.body("Post").unwrap());
|
||||
let overlap = solvecraft_kernel::boolean(&arc.body, &post.body, solvecraft_kernel::BoolOp::Intersect)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|b| solvecraft_kernel::measure(&b).map(|m| m.volume).unwrap_or(0.0))
|
||||
.unwrap_or(0.0);
|
||||
assert!(overlap < 1e-3, "no overlap: {overlap}");
|
||||
let v = solvecraft_kernel::measure(&arc.body).unwrap().volume;
|
||||
let full = PI * (225.0 - 100.0) * 5.0;
|
||||
// The post is at a quarter turn one way (three quarters the other): the ring stops
|
||||
// short of the post's centre line, within its half-width of it.
|
||||
let turned = v / full * 360.0;
|
||||
let to_centre = if turned < 180.0 { 90.0 } else { 270.0 };
|
||||
assert!(turned < to_centre && turned > to_centre - 30.0, "{turned}°");
|
||||
return;
|
||||
}
|
||||
panic!("neither post was reached");
|
||||
}
|
||||
@@ -425,5 +425,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod extent_tests;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
Reference in new issue
Block a user