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