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}