1use anyhow::Result;
4use kcmc::ModelingCmd;
5use kcmc::each_cmd as mcmd;
6use kcmc::length_unit::LengthUnit;
7use kcmc::shared::BodyType;
8use kittycad_modeling_cmds::id::ModelingCmdId;
9use kittycad_modeling_cmds::shared::RelativeTo;
10use kittycad_modeling_cmds::{self as kcmc};
11use serde::Serialize;
12
13use super::DEFAULT_TOLERANCE_MM;
14use super::args::TyF64;
15use crate::errors::KclError;
16use crate::errors::KclErrorDetails;
17use crate::execution::ExecState;
18use crate::execution::Helix;
19use crate::execution::KclValue;
20use crate::execution::ModelingCmdMeta;
21use crate::execution::ProfileClosed;
22use crate::execution::Segment;
23use crate::execution::Sketch;
24use crate::execution::Solid;
25use crate::execution::types::ArrayLen;
26use crate::execution::types::RuntimeType;
27use crate::parsing::ast::types::TagNode;
28use crate::std::Args;
29use crate::std::extrude::build_segment_surface_sketch;
30use crate::std::extrude::do_post_extrude;
31use crate::std::revolve::coerce_revolve_targets;
32
33#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
35#[ts(export)]
36#[serde(untagged)]
37#[allow(clippy::large_enum_variant)]
38pub enum SweepPath {
39 Sketch(Sketch),
40 Helix(Box<Helix>),
41 Segments(Vec<Segment>),
42}
43
44#[allow(clippy::large_enum_variant)]
46enum InnerSweepPath {
47 Sketch(Sketch),
48 Helix(Box<Helix>),
49}
50
51pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
53 let sketch_values = args.get_unlabeled_kw_arg(
54 "sketches",
55 &RuntimeType::Array(
56 Box::new(RuntimeType::Union(vec![RuntimeType::sketch(), RuntimeType::segment()])),
57 ArrayLen::Minimum(1),
58 ),
59 exec_state,
60 )?;
61 let path: SweepPath = args.get_kw_arg(
62 "path",
63 &RuntimeType::Union(vec![
64 RuntimeType::sketch(),
65 RuntimeType::helix(),
66 RuntimeType::Array(Box::new(RuntimeType::segment()), ArrayLen::Minimum(1)),
67 ]),
68 exec_state,
69 )?;
70 let sectional = args.get_kw_arg_opt("sectional", &RuntimeType::bool(), exec_state)?;
71 let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
72 let relative_to: Option<String> = args.get_kw_arg_opt("relativeTo", &RuntimeType::string(), exec_state)?;
73 let tag_start = args.get_kw_arg_opt("tagStart", &RuntimeType::tag_decl(), exec_state)?;
74 let tag_end = args.get_kw_arg_opt("tagEnd", &RuntimeType::tag_decl(), exec_state)?;
75 let body_type: Option<BodyType> = args.get_kw_arg_opt("bodyType", &RuntimeType::string(), exec_state)?;
76 let version: Option<TyF64> = args.get_kw_arg_opt("version", &RuntimeType::count(), exec_state)?;
77 let path = match path {
78 SweepPath::Segments(segments) => InnerSweepPath::Sketch(
79 build_segment_surface_sketch(segments, exec_state, &args.ctx, args.source_range).await?,
80 ),
81 SweepPath::Sketch(sketch) => InnerSweepPath::Sketch(sketch),
82 SweepPath::Helix(helix) => InnerSweepPath::Helix(helix),
83 };
84
85 let sketches = coerce_revolve_targets(
86 sketch_values,
87 body_type.unwrap_or_default(),
88 tag_start.as_ref(),
89 tag_end.as_ref(),
90 exec_state,
91 &args.ctx,
92 args.source_range,
93 )
94 .await?;
95
96 let value = inner_sweep(
97 sketches,
98 path,
99 sectional,
100 tolerance,
101 relative_to,
102 tag_start,
103 tag_end,
104 body_type,
105 version,
106 exec_state,
107 args,
108 )
109 .await?;
110 Ok(value.into())
111}
112
113#[allow(clippy::too_many_arguments)]
114async fn inner_sweep(
115 sketches: Vec<Sketch>,
116 path: InnerSweepPath,
117 sectional: Option<bool>,
118 tolerance: Option<TyF64>,
119 relative_to: Option<String>,
120 tag_start: Option<TagNode>,
121 tag_end: Option<TagNode>,
122 body_type: Option<BodyType>,
123 version: Option<TyF64>,
124 exec_state: &mut ExecState,
125 args: Args,
126) -> Result<Vec<Solid>, KclError> {
127 let body_type = body_type.unwrap_or_default();
128 if matches!(body_type, BodyType::Solid) && sketches.iter().any(|sk| matches!(sk.is_closed, ProfileClosed::No)) {
129 return Err(KclError::new_semantic(KclErrorDetails::new(
130 "Cannot solid sweep an open profile. Either close the profile, or use a surface sweep.".to_owned(),
131 vec![args.source_range],
132 )));
133 }
134
135 let trajectory = ModelingCmdId::from(match path {
136 InnerSweepPath::Sketch(sketch) => sketch.id,
137 InnerSweepPath::Helix(helix) => helix.value,
138 });
139 let relative_to = match relative_to.as_deref() {
140 Some("sketchPlane") => RelativeTo::SketchPlane,
141 Some("trajectoryCurve") | None => RelativeTo::TrajectoryCurve,
142 Some(_) => {
143 return Err(KclError::new_syntax(crate::errors::KclErrorDetails::new(
144 "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
145 vec![args.source_range],
146 )));
147 }
148 };
149
150 let mut solids = Vec::new();
151 for sketch in &sketches {
152 let id = exec_state.next_uuid();
153 exec_state
154 .batch_modeling_cmd(
155 ModelingCmdMeta::from_args_id(exec_state, &args, id),
156 ModelingCmd::from(
157 mcmd::Sweep::builder()
158 .target(sketch.id.into())
159 .trajectory(trajectory)
160 .sectional(sectional.unwrap_or(false))
161 .tolerance(LengthUnit(
162 tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM),
163 ))
164 .relative_to(relative_to)
165 .body_type(body_type)
166 .maybe_version(version.as_ref().map(|t| t.n as u8))
167 .build(),
168 ),
169 )
170 .await?;
171
172 solids.push(
173 do_post_extrude(
174 sketch,
175 id.into(),
176 sectional.unwrap_or(false),
177 &super::extrude::NamedCapTags {
178 start: tag_start.as_ref(),
179 end: tag_end.as_ref(),
180 },
181 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
182 exec_state,
183 &args,
184 None,
185 None,
186 body_type,
187 crate::std::extrude::BeingExtruded::Sketch,
188 )
189 .await?,
190 );
191 }
192
193 exec_state
195 .batch_modeling_cmd(
196 ModelingCmdMeta::from_args(exec_state, &args),
197 ModelingCmd::from(
198 mcmd::ObjectVisible::builder()
199 .object_id(trajectory.into())
200 .hidden(true)
201 .build(),
202 ),
203 )
204 .await?;
205
206 Ok(solids)
207}