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    unlabeled_first = true,
164    args = {
165        sketches = { docs = "The sketch or set of sketches that should be swept in space" },
166        path = { docs = "The path to sweep the sketch along" },
167        sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
168        tolerance = { docs = "Tolerance for this operation" },
169        relative_to = { docs = "What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to trajectoryCurve."},
170        tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
171        tag_end = { docs = "A named tag for the face at the end of the sweep" },
172    },
173    tags = ["sketch"]
174}]
175#[allow(clippy::too_many_arguments)]
176async fn inner_sweep(
177    sketches: Vec<Sketch>,
178    path: SweepPath,
179    sectional: Option<bool>,
180    tolerance: Option<TyF64>,
181    relative_to: Option<String>,
182    tag_start: Option<TagNode>,
183    tag_end: Option<TagNode>,
184    exec_state: &mut ExecState,
185    args: Args,
186) -> Result<Vec<Solid>, KclError> {
187    let trajectory = match path {
188        SweepPath::Sketch(sketch) => sketch.id.into(),
189        SweepPath::Helix(helix) => helix.value.into(),
190    };
191    let relative_to = match relative_to.as_deref() {
192        Some("sketchPlane") => RelativeTo::SketchPlane,
193        Some("trajectoryCurve") | None => RelativeTo::TrajectoryCurve,
194        Some(_) => {
195            return Err(KclError::Syntax(crate::errors::KclErrorDetails::new(
196                "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
197                vec![args.source_range],
198            )))
199        }
200    };
201
202    let mut solids = Vec::new();
203    for sketch in &sketches {
204        let id = exec_state.next_uuid();
205        args.batch_modeling_cmd(
206            id,
207            ModelingCmd::from(mcmd::Sweep {
208                target: sketch.id.into(),
209                trajectory,
210                sectional: sectional.unwrap_or(false),
211                tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
212                relative_to,
213            }),
214        )
215        .await?;
216
217        solids.push(
218            do_post_extrude(
219                sketch,
220                id.into(),
221                TyF64::new(0.0, NumericType::mm()),
222                sectional.unwrap_or(false),
223                &super::extrude::NamedCapTags {
224                    start: tag_start.as_ref(),
225                    end: tag_end.as_ref(),
226                },
227                exec_state,
228                &args,
229                None,
230            )
231            .await?,
232        );
233    }
234
235    // Hide the artifact from the sketch or helix.
236    args.batch_modeling_cmd(
237        exec_state.next_uuid(),
238        ModelingCmd::from(mcmd::ObjectVisible {
239            object_id: trajectory.into(),
240            hidden: true,
241        }),
242    )
243    .await?;
244
245    Ok(solids)
246}