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::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 tag_start = args.get_kw_arg_opt("tagStart")?;
41    let tag_end = args.get_kw_arg_opt("tagEnd")?;
42
43    let value = inner_sweep(
44        sketches, path, sectional, tolerance, tag_start, tag_end, exec_state, args,
45    )
46    .await?;
47    Ok(value.into())
48}
49
50/// Extrude a sketch along a path.
51///
52/// This, like extrude, is able to create a 3-dimensional solid from a
53/// 2-dimensional sketch. However, unlike extrude, this creates a solid
54/// by using the extent of the sketch as its path. This is useful for
55/// creating more complex shapes that can't be created with a simple
56/// extrusion.
57///
58/// You can provide more than one sketch to sweep, and they will all be
59/// swept along the same path.
60///
61/// ```no_run
62/// // Create a pipe using a sweep.
63///
64/// // Create a path for the sweep.
65/// sweepPath = startSketchOn(XZ)
66///     |> startProfile(at = [0.05, 0.05])
67///     |> line(end = [0, 7])
68///     |> tangentialArc(angle = 90, radius = 5)
69///     |> line(end = [-3, 0])
70///     |> tangentialArc(angle = -90, radius = 5)
71///     |> line(end = [0, 7])
72///
73/// // Create a hole for the pipe.
74/// pipeHole = startSketchOn(XY)
75///     |> circle(
76///         center = [0, 0],
77///         radius = 1.5,
78///     )
79///
80/// sweepSketch = startSketchOn(XY)
81///     |> circle(
82///         center = [0, 0],
83///         radius = 2,
84///         )              
85///     |> subtract2d(tool = pipeHole)
86///     |> sweep(path = sweepPath)   
87/// ```
88///
89/// ```no_run
90/// // Create a spring by sweeping around a helix path.
91///
92/// // Create a helix around the Z axis.
93/// helixPath = helix(
94///     angleStart = 0,
95///     ccw = true,
96///     revolutions = 4,
97///     length = 10,
98///     radius = 5,
99///     axis = Z,
100///  )
101///
102///
103/// // Create a spring by sweeping around the helix path.
104/// springSketch = startSketchOn(YZ)
105///     |> circle( center = [0, 0], radius = 1)
106///     |> sweep(path = helixPath)
107/// ```
108///
109/// ```no_run
110/// // Sweep two sketches along the same path.
111///
112/// sketch001 = startSketchOn(XY)
113/// rectangleSketch = startProfile(sketch001, at = [-200, 23.86])
114///     |> angledLine(angle = 0, length = 73.47, tag = $rectangleSegmentA001)
115///     |> angledLine(
116///         angle = segAng(rectangleSegmentA001) - 90,
117///         length = 50.61,
118///     )
119///     |> angledLine(
120///         angle = segAng(rectangleSegmentA001),
121///         length = -segLen(rectangleSegmentA001),
122///     )
123///     |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
124///     |> close()
125///
126/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
127///
128/// sketch002 = startSketchOn(YZ)
129/// sweepPath = startProfile(sketch002, at = [0, 0])
130///     |> yLine(length = 231.81)
131///     |> tangentialArc(radius = 80, angle = -90)
132///     |> xLine(length = 384.93)
133///
134/// sweep([rectangleSketch, circleSketch], path = sweepPath)
135/// ```
136/// ```
137/// // Sectionally sweep one sketch along the path
138///
139/// sketch001 = startSketchOn(XY)
140/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
141///
142/// sketch002 = startSketchOn(YZ)
143/// sweepPath = startProfile(sketch002, at = [0, 0])
144///     |> yLine(length = 231.81)
145///     |> tangentialArc(radius = 80, angle = -90)
146///     |> xLine(length = 384.93)
147///
148/// sweep(circleSketch, path = sweepPath, sectional = true)
149/// ```
150
151#[stdlib {
152    name = "sweep",
153    feature_tree_operation = true,
154    keywords = true,
155    unlabeled_first = true,
156    args = {
157        sketches = { docs = "The sketch or set of sketches that should be swept in space" },
158        path = { docs = "The path to sweep the sketch along" },
159        sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
160        tolerance = { docs = "Tolerance for this operation" },
161        tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
162        tag_end = { docs = "A named tag for the face at the end of the sweep" },
163    },
164    tags = ["sketch"]
165}]
166#[allow(clippy::too_many_arguments)]
167async fn inner_sweep(
168    sketches: Vec<Sketch>,
169    path: SweepPath,
170    sectional: Option<bool>,
171    tolerance: Option<TyF64>,
172    tag_start: Option<TagNode>,
173    tag_end: Option<TagNode>,
174    exec_state: &mut ExecState,
175    args: Args,
176) -> Result<Vec<Solid>, KclError> {
177    let trajectory = match path {
178        SweepPath::Sketch(sketch) => sketch.id.into(),
179        SweepPath::Helix(helix) => helix.value.into(),
180    };
181
182    let mut solids = Vec::new();
183    for sketch in &sketches {
184        let id = exec_state.next_uuid();
185        args.batch_modeling_cmd(
186            id,
187            ModelingCmd::from(mcmd::Sweep {
188                target: sketch.id.into(),
189                trajectory,
190                sectional: sectional.unwrap_or(false),
191                tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
192            }),
193        )
194        .await?;
195
196        solids.push(
197            do_post_extrude(
198                sketch,
199                #[cfg(feature = "artifact-graph")]
200                id.into(),
201                TyF64::new(0.0, NumericType::mm()),
202                sectional.unwrap_or(false),
203                &super::extrude::NamedCapTags {
204                    start: tag_start.as_ref(),
205                    end: tag_end.as_ref(),
206                },
207                exec_state,
208                &args,
209            )
210            .await?,
211        );
212    }
213
214    // Hide the artifact from the sketch or helix.
215    args.batch_modeling_cmd(
216        exec_state.next_uuid(),
217        ModelingCmd::from(mcmd::ObjectVisible {
218            object_id: trajectory.into(),
219            hidden: true,
220        }),
221    )
222    .await?;
223
224    Ok(solids)
225}