Shell Outside and Both (walls grown out of the faces, or half out and half in: an offset body less a cavity) and tangent-chain face selection (open faces joined smoothly to the picked ones, by the surface normals either side of each edge)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-09 12:47:56 +00:00
1 parent a46d00f361
commit 8e36d5d2fe
4 files changed
+163 -4

No files matched your search

+16 -2
View File
@@ -75,7 +75,7 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("solid.shell", "Shell", shell)
.at("SOLID", "MODIFY")
.icon("shell")
.params("faces: [[x,y,z] points on the faces to remove]; thickness: expr (inside); body?"),
.params("faces: [[x,y,z] points on the faces to remove]; thickness: expr; direction?: inside|outside|both; tangent_chain?: bool (also open faces joined smoothly to the picked ones); body?"),
CommandSpec::new("solid.draft", "Draft", draft)
.at("SOLID", "MODIFY")
.icon("draft")
@@ -992,7 +992,21 @@ fn shell(s: &mut Session, p: &Value) -> Result<Value> {
let faces = face_points(p, cmd)?;
let thickness = req_expr(cmd, p, "thickness")?;
check_expr(s, &thickness, Kind::Length, cmd, "thickness")?;
add_feature(s, p, FeatureKind::Shell { faces, thickness, body: str_(p, "body").map(str::to_string) })
let direction = str_(p, "direction").map(str::to_ascii_lowercase).unwrap_or_default();
if !matches!(direction.as_str(), "" | "inside" | "outside" | "both") {
return Err(bad(cmd, "`direction` must be inside, outside or both"));
}
add_feature(
s,
p,
FeatureKind::Shell {
faces,
thickness,
body: str_(p, "body").map(str::to_string),
direction: if direction == "inside" { String::new() } else { direction },
tangent_chain: bool_(p, "tangent_chain").unwrap_or(false),
},
)
}
fn draft(s: &mut Session, p: &Value) -> Result<Value> {
+27
View File
@@ -1715,3 +1715,30 @@ fn revolve_two_sides_symmetric_and_to_object() {
let (v, _) = ring(json!({"to": corner}));
assert!(rel(v, v90) < 1e-3, "{v} {v90}");
}
/// Shell Inside, Outside and Both, and a tangent chain of open faces.
#[test]
fn shell_directions_and_tangent_chain() {
let shell = |extra: Value| {
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 20}));
let mut p = json!({"faces": [[20, 15, 20]], "thickness": 2});
for (k, v) in extra.as_object().into_iter().flatten() {
p[k] = v.clone();
}
run(&mut s, "solid.shell", p);
let m = run(&mut s, "inspect.measure", json!({}));
(m["total"]["volume_mm3"].as_f64().unwrap_or(0.0), m["bodies"][0]["bbox"].clone())
};
let (v, _) = shell(json!({}));
assert!(rel(v, 40.0 * 30.0 * 20.0 - 36.0 * 26.0 * 18.0) < 1e-6, "{v}");
// Outside: the walls grow out; the original box is the cavity.
let (v, b) = shell(json!({"direction": "outside"}));
assert!(rel(v, 44.0 * 34.0 * 22.0 - 40.0 * 30.0 * 20.0) < 1e-6, "{v} {b}");
// Both: 1 mm out, 1 mm in.
let (v, _) = shell(json!({"direction": "both"}));
assert!(rel(v, 42.0 * 32.0 * 21.0 - 38.0 * 28.0 * 19.0) < 1e-6, "{v}");
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 20}));
assert!(s.execute("solid.shell", &json!({"faces": [[20, 15, 20]], "thickness": 2, "direction": "sideways"})).is_err());
}