1use anyhow::Result;
4use kcl_derive_docs::stdlib;
5use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
6use kittycad_modeling_cmds::{self as kcmc, shared::RelativeTo};
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#[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
30pub 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 relative_to: Option<String> = args.get_kw_arg_opt_typed("relativeTo", &RuntimeType::string(), exec_state)?;
41    let tag_start = args.get_kw_arg_opt("tagStart")?;
42    let tag_end = args.get_kw_arg_opt("tagEnd")?;
43
44    let value = inner_sweep(
45        sketches,
46        path,
47        sectional,
48        tolerance,
49        relative_to,
50        tag_start,
51        tag_end,
52        exec_state,
53        args,
54    )
55    .await?;
56    Ok(value.into())
57}
58
59#[stdlib {
161    name = "sweep",
162    feature_tree_operation = true,
163    keywords = true,
164    unlabeled_first = true,
165    args = {
166        sketches = { docs = "The sketch or set of sketches that should be swept in space" },
167        path = { docs = "The path to sweep the sketch along" },
168        sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
169        tolerance = { docs = "Tolerance for this operation" },
170        relative_to = { docs = "What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to trajectoryCurve."},
171        tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
172        tag_end = { docs = "A named tag for the face at the end of the sweep" },
173    },
174    tags = ["sketch"]
175}]
176#[allow(clippy::too_many_arguments)]
177async fn inner_sweep(
178    sketches: Vec<Sketch>,
179    path: SweepPath,
180    sectional: Option<bool>,
181    tolerance: Option<TyF64>,
182    relative_to: Option<String>,
183    tag_start: Option<TagNode>,
184    tag_end: Option<TagNode>,
185    exec_state: &mut ExecState,
186    args: Args,
187) -> Result<Vec<Solid>, KclError> {
188    let trajectory = match path {
189        SweepPath::Sketch(sketch) => sketch.id.into(),
190        SweepPath::Helix(helix) => helix.value.into(),
191    };
192    let relative_to = match relative_to.as_deref() {
193        Some("sketchPlane") => RelativeTo::SketchPlane,
194        Some("trajectoryCurve") | None => RelativeTo::TrajectoryCurve,
195        Some(_) => {
196            return Err(KclError::Syntax(crate::errors::KclErrorDetails {
197                source_ranges: vec![args.source_range],
198                message: "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
199            }))
200        }
201    };
202
203    let mut solids = Vec::new();
204    for sketch in &sketches {
205        let id = exec_state.next_uuid();
206        args.batch_modeling_cmd(
207            id,
208            ModelingCmd::from(mcmd::Sweep {
209                target: sketch.id.into(),
210                trajectory,
211                sectional: sectional.unwrap_or(false),
212                tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
213                relative_to,
214            }),
215        )
216        .await?;
217
218        solids.push(
219            do_post_extrude(
220                sketch,
221                #[cfg(feature = "artifact-graph")]
222                id.into(),
223                TyF64::new(0.0, NumericType::mm()),
224                sectional.unwrap_or(false),
225                &super::extrude::NamedCapTags {
226                    start: tag_start.as_ref(),
227                    end: tag_end.as_ref(),
228                },
229                exec_state,
230                &args,
231                None,
232            )
233            .await?,
234        );
235    }
236
237    args.batch_modeling_cmd(
239        exec_state.next_uuid(),
240        ModelingCmd::from(mcmd::ObjectVisible {
241            object_id: trajectory.into(),
242            hidden: true,
243        }),
244    )
245    .await?;
246
247    Ok(solids)
248}