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}