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