kcl_lib/std/
sweep.rs

1//! Standard library sweep.
2
3use anyhow::Result;
4use kcl_derive_docs::stdlib;
5use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
6use kittycad_modeling_cmds::{self as kcmc};
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10use crate::{
11    errors::KclError,
12    execution::{types::RuntimeType, ExecState, Helix, KclValue, Sketch, Solid},
13    parsing::ast::types::TagNode,
14    std::{extrude::do_post_extrude, fillet::default_tolerance, Args},
15};
16
17/// A path to sweep along.
18#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
19#[ts(export)]
20#[serde(untagged)]
21pub enum SweepPath {
22    Sketch(Sketch),
23    Helix(Box<Helix>),
24}
25
26/// Extrude a sketch along a path.
27pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
28    let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
29    let path: SweepPath = args.get_kw_arg("path")?;
30    let sectional = args.get_kw_arg_opt("sectional")?;
31    let tolerance = args.get_kw_arg_opt("tolerance")?;
32    let tag_start = args.get_kw_arg_opt("tagStart")?;
33    let tag_end = args.get_kw_arg_opt("tagEnd")?;
34
35    let value = inner_sweep(
36        sketches, path, sectional, tolerance, tag_start, tag_end, exec_state, args,
37    )
38    .await?;
39    Ok(value.into())
40}
41
42/// Extrude a sketch along a path.
43///
44/// This, like extrude, is able to create a 3-dimensional solid from a
45/// 2-dimensional sketch. However, unlike extrude, this creates a solid
46/// by using the extent of the sketch as its path. This is useful for
47/// creating more complex shapes that can't be created with a simple
48/// extrusion.
49///
50/// You can provide more than one sketch to sweep, and they will all be
51/// swept along the same path.
52///
53/// ```no_run
54/// // Create a pipe using a sweep.
55///
56/// // Create a path for the sweep.
57/// sweepPath = startSketchOn('XZ')
58///     |> startProfileAt([0.05, 0.05], %)
59///     |> line(end = [0, 7])
60///     |> tangentialArc({
61///         offset: 90,
62///         radius: 5
63///     }, %)
64///     |> line(end = [-3, 0])
65///     |> tangentialArc({
66///         offset: -90,
67///         radius: 5
68///     }, %)
69///     |> line(end = [0, 7])
70///
71/// // Create a hole for the pipe.
72/// pipeHole = startSketchOn('XY')
73///     |> circle(
74///         center = [0, 0],
75///         radius = 1.5,
76///     )
77///
78/// sweepSketch = startSketchOn('XY')
79///     |> circle(
80///         center = [0, 0],
81///         radius = 2,
82///         )              
83///     |> hole(pipeHole, %)
84///     |> sweep(path = sweepPath)   
85/// ```
86///
87/// ```no_run
88/// // Create a spring by sweeping around a helix path.
89///
90/// // Create a helix around the Z axis.
91/// helixPath = helix(
92///     angleStart = 0,
93///     ccw = true,
94///     revolutions = 4,
95///     length = 10,
96///     radius = 5,
97///     axis = 'Z',
98///  )
99///
100///
101/// // Create a spring by sweeping around the helix path.
102/// springSketch = startSketchOn('YZ')
103///     |> circle( center = [0, 0], radius = 1)
104///     |> sweep(path = helixPath)
105/// ```
106///
107/// ```
108/// // Sweep two sketches along the same path.
109///
110/// sketch001 = startSketchOn('XY')
111/// rectangleSketch = startProfileAt([-200, 23.86], sketch001)
112///     |> angledLine([0, 73.47], %, $rectangleSegmentA001)
113///     |> angledLine([
114///         segAng(rectangleSegmentA001) - 90,
115///         50.61
116///     ], %)
117///     |> angledLine([
118///         segAng(rectangleSegmentA001),
119///         -segLen(rectangleSegmentA001)
120///     ], %)
121///     |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
122///     |> close()
123///
124/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
125///
126/// sketch002 = startSketchOn('YZ')
127/// sweepPath = startProfileAt([0, 0], sketch002)
128///     |> yLine(length = 231.81)
129///     |> tangentialArc({
130///         radius = 80,
131///         offset = -90,
132///     }, %)
133///     |> xLine(length = 384.93)
134///
135/// sweep([rectangleSketch, circleSketch], path = sweepPath)
136/// ```
137/// ```
138/// // Sectionally sweep one sketch along the path
139///
140/// sketch001 = startSketchOn('XY')
141/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
142///
143/// sketch002 = startSketchOn('YZ')
144/// sweepPath = startProfileAt([0, 0], sketch002)
145///     |> yLine(length = 231.81)
146///     |> tangentialArc({
147///         radius = 80,
148///         offset = -90,
149///     }, %)
150///     |> xLine(length = 384.93)
151///
152/// sweep(circleSketch, path = sweepPath, sectional = true)
153/// ```
154
155#[stdlib {
156    name = "sweep",
157    feature_tree_operation = true,
158    keywords = true,
159    unlabeled_first = true,
160    args = {
161        sketches = { docs = "The sketch or set of sketches that should be swept in space" },
162        path = { docs = "The path to sweep the sketch along" },
163        sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
164        tolerance = { docs = "Tolerance for this operation" },
165        tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
166        tag_end = { docs = "A named tag for the face at the end of the sweep" },
167    }
168}]
169#[allow(clippy::too_many_arguments)]
170async fn inner_sweep(
171    sketches: Vec<Sketch>,
172    path: SweepPath,
173    sectional: Option<bool>,
174    tolerance: Option<f64>,
175    tag_start: Option<TagNode>,
176    tag_end: Option<TagNode>,
177    exec_state: &mut ExecState,
178    args: Args,
179) -> Result<Vec<Solid>, KclError> {
180    let trajectory = match path {
181        SweepPath::Sketch(sketch) => sketch.id.into(),
182        SweepPath::Helix(helix) => helix.value.into(),
183    };
184
185    let mut solids = Vec::new();
186    for sketch in &sketches {
187        let id = exec_state.next_uuid();
188        args.batch_modeling_cmd(
189            id,
190            ModelingCmd::from(mcmd::Sweep {
191                target: sketch.id.into(),
192                trajectory,
193                sectional: sectional.unwrap_or(false),
194                tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
195            }),
196        )
197        .await?;
198
199        solids.push(
200            do_post_extrude(
201                sketch,
202                id.into(),
203                0.0,
204                sectional.unwrap_or(false),
205                &super::extrude::NamedCapTags {
206                    start: tag_start.as_ref(),
207                    end: tag_end.as_ref(),
208                },
209                exec_state,
210                &args,
211            )
212            .await?,
213        );
214    }
215
216    Ok(solids)
217}