Revolve/Sweep multiple sketches at once (#5779)

* revolve multiple sketches at once

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* do the same for swweep

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* do the same for swweep

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-03-13 09:38:22 -07:00
committed by GitHub
parent c441a3ab1c
commit 80f78e1c61
28 changed files with 1153 additions and 899 deletions

View File

@ -522,13 +522,6 @@ impl Args {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_sketch<'a, T>(&'a self) -> Result<(T, Sketch), KclError>
where
T: serde::de::DeserializeOwned + FromArgs<'a>,
{
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_sketch_set<'a, T>(&'a self) -> Result<(T, SketchSet), KclError>
where
T: serde::de::DeserializeOwned + FromArgs<'a>,

View File

@ -34,6 +34,9 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// create new 3-dimensional volume, or if extruded into an existing volume,
/// cut into an existing solid.
///
/// You can provide more than one sketch to extrude, and they will all be
/// extruded in the same direction.
///
/// ```no_run
/// example = startSketchOn('XZ')
/// |> startProfileAt([0, 0], %)
@ -82,7 +85,7 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
keywords = true,
unlabeled_first = true,
args = {
sketch_set = { docs = "Which sketches should be extruded"},
sketch_set = { docs = "Which sketch or set of sketches should be extruded"},
length = { docs = "How far to extrude the given sketches"},
}
}]

View File

@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{ExecState, KclValue, Sketch, Solid},
execution::{ExecState, KclValue, Sketch, SketchSet, SolidSet},
std::{axis_or_reference::Axis2dOrEdgeReference, extrude::do_post_extrude, fillet::default_tolerance, Args},
};
@ -28,12 +28,12 @@ pub struct RevolveData {
pub tolerance: Option<f64>,
}
/// Revolve a sketch around an axis.
/// Revolve a sketch or set of sketches around an axis.
pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch): (RevolveData, Sketch) = args.get_data_and_sketch()?;
let (data, sketch_set): (RevolveData, SketchSet) = args.get_data_and_sketch_set()?;
let value = inner_revolve(data, sketch, exec_state, args).await?;
Ok(KclValue::Solid { value })
let value = inner_revolve(data, sketch_set, exec_state, args).await?;
Ok(value.into())
}
/// Rotate a sketch around some provided axis, creating a solid from its extent.
@ -46,6 +46,9 @@ pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
///
/// Revolve occurs around a local sketch axis rather than a global axis.
///
/// You can provide more than one sketch to revolve, and they will all be
/// revolved around the same axis.
///
/// ```no_run
/// part001 = startSketchOn('XY')
/// |> startProfileAt([4, 12], %)
@ -174,16 +177,39 @@ pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// }
/// }, sketch001)
/// ```
///
/// ```no_run
/// // Revolve two sketches around the same axis.
///
/// sketch001 = startSketchOn('XY')
/// profile001 = startProfileAt([4, 8], sketch001)
/// |> xLine(length = 3)
/// |> yLine(length = -3)
/// |> xLine(length = -3)
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// profile002 = startProfileAt([-5, 8], sketch001)
/// |> xLine(length = 3)
/// |> yLine(length = -3)
/// |> xLine(length = -3)
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// revolve({
/// axis = "X",
/// }, [profile001, profile002])
/// ```
#[stdlib {
name = "revolve",
feature_tree_operation = true,
}]
async fn inner_revolve(
data: RevolveData,
sketch: Sketch,
sketch_set: SketchSet,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
) -> Result<SolidSet, KclError> {
if let Some(angle) = data.angle {
// Return an error if the angle is zero.
// We don't use validate() here because we want to return a specific error message that is
@ -198,37 +224,44 @@ async fn inner_revolve(
let angle = Angle::from_degrees(data.angle.unwrap_or(360.0));
let id = exec_state.next_uuid();
match data.axis {
Axis2dOrEdgeReference::Axis(axis) => {
let (axis, origin) = axis.axis_and_origin()?;
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::Revolve {
angle,
target: sketch.id.into(),
axis,
origin,
tolerance: LengthUnit(data.tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
axis_is_2d: true,
}),
)
.await?;
}
Axis2dOrEdgeReference::Edge(edge) => {
let edge_id = edge.get_engine_id(exec_state, &args)?;
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::RevolveAboutEdge {
angle,
target: sketch.id.into(),
edge_id,
tolerance: LengthUnit(data.tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
}),
)
.await?;
let sketches: Vec<Sketch> = sketch_set.into();
let mut solids = Vec::new();
for sketch in &sketches {
let id = exec_state.next_uuid();
match &data.axis {
Axis2dOrEdgeReference::Axis(axis) => {
let (axis, origin) = axis.axis_and_origin()?;
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::Revolve {
angle,
target: sketch.id.into(),
axis,
origin,
tolerance: LengthUnit(data.tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
axis_is_2d: true,
}),
)
.await?;
}
Axis2dOrEdgeReference::Edge(edge) => {
let edge_id = edge.get_engine_id(exec_state, &args)?;
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::RevolveAboutEdge {
angle,
target: sketch.id.into(),
edge_id,
tolerance: LengthUnit(data.tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
}),
)
.await?;
}
}
solids.push(do_post_extrude(sketch.clone(), id.into(), 0.0, exec_state, args.clone()).await?);
}
do_post_extrude(sketch, id.into(), 0.0, exec_state, args).await
Ok(solids.into())
}

View File

@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use crate::{
errors::KclError,
execution::{ExecState, Helix, KclValue, Sketch, Solid},
execution::{ExecState, Helix, KclValue, Sketch, SketchSet, SolidSet},
std::{extrude::do_post_extrude, fillet::default_tolerance, Args},
};
@ -30,7 +30,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let tolerance = args.get_kw_arg_opt("tolerance")?;
let value = inner_sweep(sketch, path, sectional, tolerance, exec_state, args).await?;
Ok(KclValue::Solid { value })
Ok(value.into())
}
/// Extrude a sketch along a path.
@ -41,6 +41,9 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// creating more complex shapes that can't be created with a simple
/// extrusion.
///
/// You can provide more than one sketch to sweep, and they will all be
/// swept along the same path.
///
/// ```no_run
/// // Create a pipe using a sweep.
///
@ -94,40 +97,79 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// |> circle( center = [0, 0], radius = 1)
/// |> sweep(path = helixPath)
/// ```
///
/// ```
/// // Sweep two sketches along the same path.
///
/// sketch001 = startSketchOn('XY')
/// rectangleSketch = startProfileAt([-200, 23.86], sketch001)
/// |> angledLine([0, 73.47], %, $rectangleSegmentA001)
/// |> angledLine([
/// segAng(rectangleSegmentA001) - 90,
/// 50.61
/// ], %)
/// |> angledLine([
/// segAng(rectangleSegmentA001),
/// -segLen(rectangleSegmentA001)
/// ], %)
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
///
/// sketch002 = startSketchOn('YZ')
/// sweepPath = startProfileAt([0, 0], sketch002)
/// |> yLine(length = 231.81)
/// |> tangentialArc({
/// radius = 80,
/// offset = -90,
/// }, %)
/// |> xLine(length = 384.93)
///
/// sweep([rectangleSketch, circleSketch], path = sweepPath)
/// ```
#[stdlib {
name = "sweep",
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
args = {
sketch = { docs = "The sketch that should be swept in space" },
sketch_set = { docs = "The sketch or set of sketches that should be swept in space" },
path = { docs = "The path to sweep the sketch along" },
sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
tolerance = { docs = "Tolerance for this operation" },
}
}]
async fn inner_sweep(
sketch: Sketch,
sketch_set: SketchSet,
path: SweepPath,
sectional: Option<bool>,
tolerance: Option<f64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::Sweep {
target: sketch.id.into(),
trajectory: match path {
SweepPath::Sketch(sketch) => sketch.id.into(),
SweepPath::Helix(helix) => helix.value.into(),
},
sectional: sectional.unwrap_or(false),
tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
}),
)
.await?;
) -> Result<SolidSet, KclError> {
let trajectory = match path {
SweepPath::Sketch(sketch) => sketch.id.into(),
SweepPath::Helix(helix) => helix.value.into(),
};
do_post_extrude(sketch, id.into(), 0.0, exec_state, args).await
let sketches: Vec<Sketch> = sketch_set.into();
let mut solids = Vec::new();
for sketch in &sketches {
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::Sweep {
target: sketch.id.into(),
trajectory,
sectional: sectional.unwrap_or(false),
tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
}),
)
.await?;
solids.push(do_post_extrude(sketch.clone(), id.into(), 0.0, exec_state, args.clone()).await?);
}
Ok(solids.into())
}