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 tag_start = args.get_kw_arg_opt("tagStart", &RuntimeType::tag_decl(), exec_state)?;
73 let tag_end = args.get_kw_arg_opt("tagEnd", &RuntimeType::tag_decl(), exec_state)?;
74 let body_type: Option<BodyType> = args.get_kw_arg_opt("bodyType", &RuntimeType::string(), exec_state)?;
75 let version: Option<u32> = args.get_kw_arg_opt("version", &RuntimeType::count(), exec_state)?;
76 let relative_to: Option<String> = args.get_kw_arg_opt("relativeTo", &RuntimeType::string(), exec_state)?;
78 let translate_profile_to_path: Option<bool> =
80 args.get_kw_arg_opt("translateProfileToPath", &RuntimeType::bool(), exec_state)?;
81 let orient_profile_perpendicular: Option<bool> =
82 args.get_kw_arg_opt("orientProfilePerpendicular", &RuntimeType::bool(), exec_state)?;
83
84 let path = match path {
85 SweepPath::Segments(segments) => InnerSweepPath::Sketch(
86 build_segment_surface_sketch(segments, exec_state, &args.ctx, args.source_range).await?,
87 ),
88 SweepPath::Sketch(sketch) => InnerSweepPath::Sketch(sketch),
89 SweepPath::Helix(helix) => InnerSweepPath::Helix(helix),
90 };
91
92 let sketches = coerce_revolve_targets(
93 sketch_values,
94 body_type.unwrap_or_default(),
95 tag_start.as_ref(),
96 tag_end.as_ref(),
97 exec_state,
98 &args.ctx,
99 args.source_range,
100 )
101 .await?;
102
103 let value = inner_sweep(
104 sketches,
105 path,
106 sectional,
107 tolerance,
108 relative_to,
109 translate_profile_to_path,
110 orient_profile_perpendicular,
111 tag_start,
112 tag_end,
113 body_type,
114 version,
115 exec_state,
116 args,
117 )
118 .await?;
119 Ok(value.into())
120}
121
122enum ProfileTransform {
123 RelativeTo(RelativeTo),
124 SeparateFlags {
125 translate_profile_to_path: bool,
126 orient_profile_perpendicular: bool,
127 },
128}
129
130impl ProfileTransform {
131 fn relative_to(&self) -> Option<RelativeTo> {
132 match self {
133 ProfileTransform::RelativeTo(relative_to) => Some(*relative_to),
134 ProfileTransform::SeparateFlags { .. } => None,
135 }
136 }
137
138 fn translate_profile_to_path(&self) -> Option<bool> {
139 match self {
140 ProfileTransform::RelativeTo(..) => None,
141 ProfileTransform::SeparateFlags {
142 translate_profile_to_path,
143 ..
144 } => Some(*translate_profile_to_path),
145 }
146 }
147 fn orient_profile_perpendicular(&self) -> Option<bool> {
148 match self {
149 ProfileTransform::RelativeTo(..) => None,
150 ProfileTransform::SeparateFlags {
151 orient_profile_perpendicular,
152 ..
153 } => Some(*orient_profile_perpendicular),
154 }
155 }
156}
157
158#[allow(clippy::too_many_arguments)]
159async fn inner_sweep(
160 sketches: Vec<Sketch>,
161 path: InnerSweepPath,
162 sectional: Option<bool>,
163 tolerance: Option<TyF64>,
164 relative_to: Option<String>,
165 translate_profile_to_path: Option<bool>,
166 orient_profile_perpendicular: Option<bool>,
167 tag_start: Option<TagNode>,
168 tag_end: Option<TagNode>,
169 body_type: Option<BodyType>,
170 version: Option<u32>,
171 exec_state: &mut ExecState,
172 args: Args,
173) -> Result<Vec<Solid>, KclError> {
174 let body_type = body_type.unwrap_or_default();
175 if matches!(body_type, BodyType::Solid) && sketches.iter().any(|sk| matches!(sk.is_closed, ProfileClosed::No)) {
176 return Err(KclError::new_semantic(KclErrorDetails::new(
177 "Cannot solid sweep an open profile. Either close the profile, or use a surface sweep.".to_owned(),
178 vec![args.source_range],
179 )));
180 }
181
182 let version = version
183 .map(|v| {
184 u8::try_from(v).map_err(|_e| {
185 KclError::new_argument(KclErrorDetails::new(
186 format!("Invalid version {}", v),
187 vec![args.source_range],
188 ))
189 })
190 })
191 .transpose()?;
192
193 let trajectory = ModelingCmdId::from(match path {
194 InnerSweepPath::Sketch(sketch) => sketch.id,
195 InnerSweepPath::Helix(helix) => helix.value,
196 });
197
198 let profile_transform = match (relative_to, translate_profile_to_path, orient_profile_perpendicular) {
199 (None, None, None) => ProfileTransform::RelativeTo(match version {
201 None | Some(1) => RelativeTo::TrajectoryCurve,
203 Some(0) => RelativeTo::TrajectoryCurve,
205 Some(2) => RelativeTo::SketchPlane,
207 Some(other) => {
209 return Err(KclError::new_argument(KclErrorDetails::new(
210 format!("Invalid version {}", other),
211 vec![args.source_range],
212 )));
213 }
214 }),
215
216 (None, translate, orient) => ProfileTransform::SeparateFlags {
218 translate_profile_to_path: translate.unwrap_or_default(),
219 orient_profile_perpendicular: orient.unwrap_or_default(),
220 },
221
222 (Some(relative_to), None, None) => ProfileTransform::RelativeTo(match relative_to.as_str() {
224 "sketchPlane" => RelativeTo::SketchPlane,
225 "trajectoryCurve" => RelativeTo::TrajectoryCurve,
226 _ => {
227 return Err(KclError::new_syntax(crate::errors::KclErrorDetails::new(
228 "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
229 vec![args.source_range],
230 )));
231 }
232 }),
233
234 (Some(_relative_to), _, _) => {
237 return Err(KclError::new_argument(crate::errors::KclErrorDetails::new(
238 "If you provide 'relativeTo', you cannot provide 'translateProfileToPath' or 'orientProfilePerpendicular'. Those arguments replace 'relativeTo', please use them instead.".to_owned(),
239 vec![args.source_range],
240 )));
241 }
242 };
243
244 let mut solids = Vec::new();
245 for sketch in &sketches {
246 let id = exec_state.next_uuid();
247 exec_state
248 .batch_modeling_cmd(
249 ModelingCmdMeta::from_args_id(exec_state, &args, id),
250 ModelingCmd::from(
251 mcmd::Sweep::builder()
252 .target(sketch.id.into())
253 .trajectory(trajectory)
254 .sectional(sectional.unwrap_or(false))
255 .tolerance(LengthUnit(
256 tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM),
257 ))
258 .maybe_relative_to(profile_transform.relative_to())
259 .maybe_orient_profile_perpendicular(profile_transform.orient_profile_perpendicular())
260 .maybe_translate_profile_to_path(profile_transform.translate_profile_to_path())
261 .body_type(body_type)
262 .maybe_version(version)
263 .build(),
264 ),
265 )
266 .await?;
267
268 solids.push(
269 do_post_extrude(
270 sketch,
271 id.into(),
272 sectional.unwrap_or(false),
273 &super::extrude::NamedCapTags {
274 start: tag_start.as_ref(),
275 end: tag_end.as_ref(),
276 },
277 kittycad_modeling_cmds::shared::ExtrudeMethod::New,
278 exec_state,
279 &args,
280 None,
281 None,
282 body_type,
283 crate::std::extrude::BeingExtruded::Sketch,
284 )
285 .await?,
286 );
287 }
288
289 exec_state
291 .batch_modeling_cmd(
292 ModelingCmdMeta::from_args(exec_state, &args),
293 ModelingCmd::from(
294 mcmd::ObjectVisible::builder()
295 .object_id(trajectory.into())
296 .hidden(true)
297 .build(),
298 ),
299 )
300 .await?;
301
302 Ok(solids)
303}