Extrude To Object (a face, vertex or point; parallel faces by their plane, others where the profile's middle meets them; to_offset past it) and From Object (start at a face or point); both kept by persistent face name
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
9ee8ba5dd7
commit
ab328ae018
5 files changed
+134
-9
No files matched your search
@@ -13,7 +13,7 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
.at("SOLID", "CREATE")
|
||||
.icon("extrude")
|
||||
.key("E")
|
||||
.params("distance: expr (or through_all: true); taper?: angle expr; sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [[curve ids]] | [{point:[x,y]}]; face?: [x,y,z] (extrude a planar body face instead of a sketch profile); direction?: positive|negative|symmetric; distance2?; start_offset?; operation?: new|join|cut|intersect|auto (cut into a body, join out of one, else new); targets?: [body]; name?; body_name?"),
|
||||
.params("distance: expr (or through_all: true); taper?: angle expr; sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [[curve ids]] | [{point:[x,y]}]; face?: [x,y,z] (extrude a planar body face instead of a sketch profile); direction?: positive|negative|symmetric; distance2?; start_offset?; to?: [x,y,z] (To Object: a face, vertex or point; to_offset?: expr past it); from?: [x,y,z] (From Object: start at that face or point); operation?: new|join|cut|intersect|auto (cut into a body, join out of one, else new); targets?: [body]; name?; body_name?"),
|
||||
CommandSpec::new("solid.revolve", "Revolve", revolve)
|
||||
.at("SOLID", "CREATE")
|
||||
.icon("revolve")
|
||||
@@ -326,7 +326,14 @@ fn extrude(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
None => (feature_sketch(s, p, cmd)?, None),
|
||||
};
|
||||
let through_all = bool_(p, "through_all").unwrap_or(false) || str_(p, "extent").is_some_and(|e| e.eq_ignore_ascii_case("through_all"));
|
||||
let distance = if through_all { expr(p, "distance").unwrap_or_else(|| "0".into()) } else { req_expr(cmd, p, "distance")? };
|
||||
let to_object = p.get("to").is_some();
|
||||
if to_object && p.get("to").and_then(vec3).is_none() {
|
||||
return Err(bad(cmd, "`to` must be a point [x, y, z] on the face, vertex or point to extrude to"));
|
||||
}
|
||||
if p.get("from").is_some() && p.get("from").and_then(vec3).is_none() {
|
||||
return Err(bad(cmd, "`from` must be a point [x, y, z] on the face or point to start from"));
|
||||
}
|
||||
let distance = if through_all || to_object { expr(p, "distance").unwrap_or_else(|| "0".into()) } else { req_expr(cmd, p, "distance")? };
|
||||
check_expr(s, &distance, Kind::Length, cmd, "distance")?;
|
||||
let taper = expr(p, "taper");
|
||||
if let Some(t) = &taper {
|
||||
@@ -352,7 +359,17 @@ fn extrude(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Some(f) => f,
|
||||
None => profiles(p, cmd)?,
|
||||
},
|
||||
extent: Extent { distance, direction, distance2, start_offset, through_all, taper },
|
||||
extent: Extent {
|
||||
distance,
|
||||
direction,
|
||||
distance2,
|
||||
start_offset,
|
||||
through_all,
|
||||
taper,
|
||||
to: p.get("to").and_then(vec3),
|
||||
to_offset: expr(p, "to_offset"),
|
||||
from: p.get("from").and_then(vec3),
|
||||
},
|
||||
operation: match str_(p, "operation") {
|
||||
Some(o) if o.eq_ignore_ascii_case("auto") => Operation::parse(auto_operation(s, p).unwrap_or("new")).unwrap_or(Operation::NewBody),
|
||||
_ => operation(p, cmd)?,
|
||||
|
||||
@@ -1655,3 +1655,31 @@ fn appearances_survive_later_features() {
|
||||
assert!(red(&mut t, &body), "the imported red face carries on");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Extrude To Object (a parallel face, with an offset; a slanted face) and From Object.
|
||||
#[test]
|
||||
fn extrude_to_and_from_objects() {
|
||||
let mut s = Session::default();
|
||||
// A 10 mm plate at z 30…40 to extrude up to, and a profile on XY.
|
||||
run(&mut s, "solid.box", json!({"corner": [0, 0, 30], "length": 40, "width": 40, "height": 10, "body_name": "Roof"}));
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "P"}));
|
||||
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [5, 5], "p1": [15, 15]}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
let top = |s: &mut Session, b: &str| run(s, "inspect.measure", json!({"bodies": [b]}))["bodies"][0]["bbox"]["max"][2].as_f64().unwrap_or(0.0);
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "P", "to": [20, 20, 30], "body_name": "Post"}));
|
||||
assert!((top(&mut s, "Post") - 30.0).abs() < 1e-6, "{}", top(&mut s, "Post"));
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "P", "to": [20, 20, 30], "to_offset": -5, "body_name": "Post"}));
|
||||
assert!((top(&mut s, "Post") - 25.0).abs() < 1e-6);
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
// From the roof's top face (z 40) to 10 mm above it.
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "P", "from": [20, 20, 40], "distance": 10, "body_name": "Post"}));
|
||||
let m = run(&mut s, "inspect.measure", json!({"bodies": ["Post"]}));
|
||||
assert!((m["bodies"][0]["bbox"]["min"][2].as_f64().unwrap_or(0.0) - 40.0).abs() < 1e-6 && (top(&mut s, "Post") - 50.0).abs() < 1e-6, "{m}");
|
||||
// The To face is kept by its persistent name, so it is followed when it moves.
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "P", "to": [20, 20, 30], "body_name": "Post"}));
|
||||
let name = s.doc.features.last().map(|f| f.face_names.clone()).unwrap_or_default();
|
||||
assert_eq!(name, vec!["F1:-z".to_string()], "the To face is stored by name");
|
||||
assert!(s.execute("solid.extrude", &json!({"sketch": "P", "to": "nope"})).is_err());
|
||||
}
|
||||
Reference in new issue
Block a user