Sweep: orientation, twist and end scale (#1269)
Part of #771 and #772. solid.sweep and surface.sweep take orientation (perpendicular, parallel or a direction), twist (up to 4 turns) and end scale (0.01 to 100), applied about the profile's normal through the path's start; default options run the old sweep, so existing sweeps are unchanged. New fields are optional, left out when unset and noted under format 21; twist and scale are model parameters. Both sweep dialogs get Orientation, Twist and End Scale rows; labels in every catalog. Checked on main + this branch: cargo check --workspace, kernel/engine/UI sweep tests, the doc suite and the robustness goldens.
This commit is contained in:
1 parent
0872eb959a
commit
5fa8e7d60a
30 files changed
+1120
-71
No files matched your search
@@ -1,7 +1,9 @@
|
||||
//! Solid feature commands: extrude, revolve, fillet, chamfer, primitives, combine, move.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_doc::{AxisRef, Direction, Extent, FeatureKind, Operation, PointExpr, ProfileSel, ThinSide, ThinWall, expr::Kind, point_expr};
|
||||
use solidcraft_doc::{
|
||||
AxisRef, Direction, Extent, FeatureKind, Operation, PointExpr, ProfileSel, SweepOrientation, ThinSide, ThinWall, expr::Kind, point_expr,
|
||||
};
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use super::{CommandSpec, not_in_sketch};
|
||||
@@ -21,7 +23,7 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
CommandSpec::new("solid.sweep", "Sweep", sweep)
|
||||
.at("SOLID", "CREATE")
|
||||
.icon("sweep")
|
||||
.params("sketch: profile sketch; profiles?; path_sketch: sketch; path: [curve ids in order]; operation?, targets?, name?, body_name?"),
|
||||
.params("sketch: profile sketch; profiles?; path_sketch: sketch; path: [curve ids in order]; orientation?: perpendicular (default: square to the path, turning with it) | parallel (as drawn: the profile moves without turning; the path must keep crossing its plane) | [x,y,z] (square to the path, its turn about the path fixed by this direction); twist?: angle expr (the profile turns this much about the path over its length; within 4 turns); scale?: expr (the profile's scale at the end, linear along the path; 0.01…100); operation?, targets?, name?, body_name?"),
|
||||
CommandSpec::new("solid.loft", "Loft", loft).at("SOLID", "CREATE").icon("loft").params("sections: [{sketch, profiles?} | {sketch, point: id} (an apex)] (2 or more, in order); operation?, targets?, name?, body_name?"),
|
||||
CommandSpec::new("solid.box", "Box", prim_box).at("SOLID", "CREATE").icon("box").params("length, width, height: expr; corner?: [x,y,z] | center?: [x,y,z] (numbers in mm or length expressions; default: the origin); operation?, targets?: [body], name?, body_name?"),
|
||||
CommandSpec::new("solid.cylinder", "Cylinder", prim_cylinder).at("SOLID", "CREATE").icon("cylinder").params("radius | diameter, height: expr; base?: [x,y,z] (centre of the bottom: numbers in mm or length expressions; default: the origin); axis?: X|Y|Z|[x,y,z] (default Z); operation?, targets?: [body], name?, body_name?"),
|
||||
@@ -1621,6 +1623,7 @@ fn sweep(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if path.is_empty() {
|
||||
return Err(bad(cmd, "`path` must list curve ids"));
|
||||
}
|
||||
let (orientation, twist, scale) = sweep_options(s, p, cmd)?;
|
||||
add_feature(
|
||||
s,
|
||||
p,
|
||||
@@ -1629,12 +1632,58 @@ fn sweep(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
profiles: profiles(p, cmd)?,
|
||||
path_sketch,
|
||||
path,
|
||||
orientation,
|
||||
twist,
|
||||
scale,
|
||||
operation: operation(p, cmd)?,
|
||||
targets: string_list(p, "targets"),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// A sweep's `orientation`, `twist` and `scale` (solid.sweep and surface.sweep), checked: an
|
||||
/// orientation the sweep knows, a twist within four turns, an end scale within 0.01…100.
|
||||
pub(super) fn sweep_options(s: &Session, p: &Value, cmd: &str) -> Result<(SweepOrientation, Option<String>, Option<String>)> {
|
||||
let orientation = match p.get("orientation") {
|
||||
None | Some(Value::Null) => SweepOrientation::Perpendicular,
|
||||
Some(Value::String(o)) if o.eq_ignore_ascii_case("perpendicular") => SweepOrientation::Perpendicular,
|
||||
Some(Value::String(o)) if o.eq_ignore_ascii_case("parallel") => SweepOrientation::Parallel,
|
||||
Some(v) => {
|
||||
let d = vec3(v)
|
||||
.or_else(|| v.get("direction").and_then(vec3))
|
||||
.ok_or_else(|| bad(cmd, "`orientation` must be perpendicular, parallel or a direction [x, y, z]"))?;
|
||||
if !(d.len() > 1e-9) {
|
||||
return Err(bad(cmd, "the `orientation` direction must not be zero"));
|
||||
}
|
||||
SweepOrientation::Direction(d)
|
||||
}
|
||||
};
|
||||
let twist = match p.get("twist") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(_) => {
|
||||
let t = expr(p, "twist").ok_or_else(|| bad(cmd, "`twist` must be an angle or an expression"))?;
|
||||
let v = s.doc.eval(&t, Kind::Angle).map_err(|e| bad(cmd, format!("twist: {e}")))?;
|
||||
if !(v.is_finite() && v.abs() <= solidcraft_kernel::SweepControl::MAX_TWIST) {
|
||||
return Err(bad(cmd, "`twist` must be within four turns (1440 deg)"));
|
||||
}
|
||||
Some(t)
|
||||
}
|
||||
};
|
||||
let scale = match p.get("scale") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(_) => {
|
||||
let k = expr(p, "scale").ok_or_else(|| bad(cmd, "`scale` must be a number or an expression"))?;
|
||||
let v = s.doc.eval(&k, Kind::Unitless).map_err(|e| bad(cmd, format!("scale: {e}")))?;
|
||||
let range = solidcraft_kernel::SweepControl::MIN_SCALE..=solidcraft_kernel::SweepControl::MAX_SCALE;
|
||||
if !(v.is_finite() && range.contains(&v)) {
|
||||
return Err(bad(cmd, "`scale` (the end scale) must be between 0.01 and 100"));
|
||||
}
|
||||
Some(k)
|
||||
}
|
||||
};
|
||||
Ok((orientation, twist, scale))
|
||||
}
|
||||
|
||||
fn loft(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "solid.loft";
|
||||
let list = p.get("sections").and_then(Value::as_array).ok_or_else(|| bad(cmd, "`sections` must be a list"))?;
|
||||
@@ -1753,6 +1802,10 @@ mod patterns_tests;
|
||||
#[path = "revolve_convention_tests.rs"]
|
||||
mod revolve_convention_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "sweep_options_tests.rs"]
|
||||
mod sweep_options_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "offset_topology_tests.rs"]
|
||||
mod offset_topology_tests;
|
||||
|
||||
Reference in new issue
Block a user