1use std::num::NonZeroU32;
4
5use anyhow::Result;
6use kcmc::{ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit};
7use kittycad_modeling_cmds as kcmc;
8
9use super::{DEFAULT_TOLERANCE_MM, args::TyF64};
10use crate::{
11 errors::{KclError, KclErrorDetails},
12 execution::{ExecState, KclValue, ModelingCmdMeta, Sketch, Solid, types::RuntimeType},
13 parsing::ast::types::TagNode,
14 std::{Args, extrude::do_post_extrude},
15};
16
17const DEFAULT_V_DEGREE: u32 = 2;
18
19pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
21 let sketches = args.get_unlabeled_kw_arg("sketches", &RuntimeType::sketches(), exec_state)?;
22 let v_degree: NonZeroU32 = args
23 .get_kw_arg_opt("vDegree", &RuntimeType::count(), exec_state)?
24 .unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap());
25 let bez_approximate_rational = args
29 .get_kw_arg_opt("bezApproximateRational", &RuntimeType::bool(), exec_state)?
30 .unwrap_or(false);
31 let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex", &RuntimeType::count(), exec_state)?;
33 let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
35 let tag_start = args.get_kw_arg_opt("tagStart", &RuntimeType::tag_decl(), exec_state)?;
36 let tag_end = args.get_kw_arg_opt("tagEnd", &RuntimeType::tag_decl(), exec_state)?;
37
38 let value = inner_loft(
39 sketches,
40 v_degree,
41 bez_approximate_rational,
42 base_curve_index,
43 tolerance,
44 tag_start,
45 tag_end,
46 exec_state,
47 args,
48 )
49 .await?;
50 Ok(KclValue::Solid { value })
51}
52
53#[allow(clippy::too_many_arguments)]
54async fn inner_loft(
55 sketches: Vec<Sketch>,
56 v_degree: NonZeroU32,
57 bez_approximate_rational: bool,
58 base_curve_index: Option<u32>,
59 tolerance: Option<TyF64>,
60 tag_start: Option<TagNode>,
61 tag_end: Option<TagNode>,
62 exec_state: &mut ExecState,
63 args: Args,
64) -> Result<Box<Solid>, KclError> {
65 if sketches.len() < 2 {
67 return Err(KclError::new_semantic(KclErrorDetails::new(
68 format!(
69 "Loft requires at least two sketches, but only {} were provided.",
70 sketches.len()
71 ),
72 vec![args.source_range],
73 )));
74 }
75
76 let id = exec_state.next_uuid();
77 exec_state
78 .batch_modeling_cmd(
79 ModelingCmdMeta::from_args_id(&args, id),
80 ModelingCmd::from(mcmd::Loft {
81 section_ids: sketches.iter().map(|group| group.id).collect(),
82 base_curve_index,
83 bez_approximate_rational,
84 tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
85 v_degree,
86 }),
87 )
88 .await?;
89
90 let mut sketch = sketches[0].clone();
92 sketch.id = id;
94 Ok(Box::new(
95 do_post_extrude(
96 &sketch,
97 id.into(),
98 false,
99 &super::extrude::NamedCapTags {
100 start: tag_start.as_ref(),
101 end: tag_end.as_ref(),
102 },
103 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
104 exec_state,
105 &args,
106 None,
107 )
108 .await?,
109 ))
110}