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