From 2608ae6761b3e0c8c3372c6bf2370a025e48537a Mon Sep 17 00:00:00 2001 From: bherbruck Date: Thu, 8 Oct 2026 07:20:38 +0000 Subject: [PATCH] Revolve a planar body face (face: point) like Extrude does, its edges projected (linked) into a sketch on it Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL --- crates/engine/src/cmd/features.rs | 19 +++++++++++++++---- crates/engine/src/tests.rs | 8 ++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/features.rs b/crates/engine/src/cmd/features.rs index 19ecf7e6..4dd7d9f0 100644 --- a/crates/engine/src/cmd/features.rs +++ b/crates/engine/src/cmd/features.rs @@ -17,7 +17,7 @@ pub static COMMANDS: &[CommandSpec] = &[ CommandSpec::new("Revolve", "Revolve", revolve) .at("SOLID", "CREATE") .icon("revolve") - .params("axis: sketch line id | x | y (sketch axes) | X|Y|Z (world); angle?: expr (default 360 deg); sketch?, profiles?, operation?, targets?, name?, body_name?"), + .params("axis: sketch line id | x | y (sketch axes) | X|Y|Z (world); angle?: expr (default 360 deg); sketch?, profiles? | face: [x,y,z] (a planar body face as the profile), operation?, targets?, name?, body_name?"), CommandSpec::new("Sweep", "Sweep", sweep) .at("SOLID", "CREATE") .icon("sweep") @@ -321,7 +321,15 @@ fn extrude(s: &mut Session, p: &Value) -> Result { fn revolve(s: &mut Session, p: &Value) -> Result { let cmd = "Revolve"; - let sketch = feature_sketch(s, p, cmd)?; + // A planar body face revolves like a profile (its edges projected into a sketch on it). + let (sketch, face_profile) = match p.get("face").and_then(vec3) { + Some(fp) => { + let (id, inside) = super::face::sketch_of_face(s, fp, cmd)?; + s.active_sketch = None; + (id, Some(ProfileSel::Points { points: vec![inside] })) + } + None => (feature_sketch(s, p, cmd)?, None), + }; let axis = if let Some(o) = p.get("axis").filter(|v| v.is_object()) { let origin = o.get("origin").and_then(vec3).unwrap_or(Vec3::ZERO); let dir = o.get("dir").and_then(vec3).ok_or_else(|| bad(cmd, "axis needs `dir`"))?; @@ -336,8 +344,11 @@ fn revolve(s: &mut Session, p: &Value) -> Result { }; let angle = expr(p, "angle").unwrap_or_else(|| "360 deg".into()); check_expr(s, &angle, Kind::Angle, cmd, "angle")?; - let kind = - FeatureKind::Revolve { sketch, profiles: profiles(p, cmd)?, axis, angle, operation: operation(p, cmd)?, targets: string_list(p, "targets") }; + let profiles = match face_profile { + Some(f) => f, + None => profiles(p, cmd)?, + }; + let kind = FeatureKind::Revolve { sketch, profiles, axis, angle, operation: operation(p, cmd)?, targets: string_list(p, "targets") }; add_feature(s, p, kind) } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 48fbdaf0..d39d5990 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -987,4 +987,12 @@ fn face_extrude_follows_edits() { assert!(rel(volume(&mut s), 40.0 * 30.0 * 30.0) < 1e-6); run(&mut s, "timeline.edit", json!({"feature": "Base", "set": {"height": "25", "length": "50"}})); assert!(rel(volume(&mut s), 50.0 * 30.0 * 35.0) < 1e-6, "{}", volume(&mut s)); + // A face revolved about a line: Pappus (area × centroid path). + let mut s = Session::default(); + run(&mut s, "PrimitiveBox", json!({"length": 40, "width": 30, "height": 20})); + let before = volume(&mut s); + run(&mut s, "Revolve", json!({"face": [40, 15, 10], "axis": {"origin": [40, 0, 30], "dir": [0, 1, 0]}, "angle": "90 deg", "operation": "new"})); + let added = volume(&mut s) - before; + let want = 30.0 * 20.0 * 20.0 * PI / 2.0; + assert!(rel(added, want) < 1e-4, "{added} vs {want}"); }