1use std::num::NonZeroU32;
4
5use anyhow::Result;
6use kcl_derive_docs::stdlib;
7use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
8use kittycad_modeling_cmds as kcmc;
9
10use crate::{
11 errors::{KclError, KclErrorDetails},
12 execution::{
13 kcl_value::{ArrayLen, RuntimeType},
14 ExecState, KclValue, PrimitiveType, Sketch, Solid,
15 },
16 parsing::ast::types::TagNode,
17 std::{extrude::do_post_extrude, fillet::default_tolerance, Args},
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_typed(
25 "sketches",
26 &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
27 exec_state,
28 )?;
29 let v_degree: NonZeroU32 = args
30 .get_kw_arg_opt("vDegree")?
31 .unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap());
32 let bez_approximate_rational = args.get_kw_arg_opt("bezApproximateRational")?.unwrap_or(false);
36 let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex")?;
38 let tolerance: Option<f64> = args.get_kw_arg_opt("tolerance")?;
40 let tag_start = args.get_kw_arg_opt("tagStart")?;
41 let tag_end = args.get_kw_arg_opt("tagEnd")?;
42
43 let value = inner_loft(
44 sketches,
45 v_degree,
46 bez_approximate_rational,
47 base_curve_index,
48 tolerance,
49 tag_start,
50 tag_end,
51 exec_state,
52 args,
53 )
54 .await?;
55 Ok(KclValue::Solid { value })
56}
57
58#[stdlib {
125 name = "loft",
126 feature_tree_operation = true,
127 keywords = true,
128 unlabeled_first = true,
129 args = {
130 sketches = {docs = "Which sketches to loft. Must include at least 2 sketches."},
131 v_degree = {docs = "Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified."},
132 bez_approximate_rational = {docs = "Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary."},
133 base_curve_index = {docs = "This can be set to override the automatically determined topological base curve, which is usually the first section encountered."},
134 tolerance = {docs = "Tolerance for the loft operation."},
135 tag_start = { docs = "A named tag for the face at the start of the loft, i.e. the original sketch" },
136 tag_end = { docs = "A named tag for the face at the end of the loft, i.e. the last sketch" },
137 }
138}]
139#[allow(clippy::too_many_arguments)]
140async fn inner_loft(
141 sketches: Vec<Sketch>,
142 v_degree: NonZeroU32,
143 bez_approximate_rational: bool,
144 base_curve_index: Option<u32>,
145 tolerance: Option<f64>,
146 tag_start: Option<TagNode>,
147 tag_end: Option<TagNode>,
148 exec_state: &mut ExecState,
149 args: Args,
150) -> Result<Box<Solid>, KclError> {
151 if sketches.len() < 2 {
153 return Err(KclError::Semantic(KclErrorDetails {
154 message: format!(
155 "Loft requires at least two sketches, but only {} were provided.",
156 sketches.len()
157 ),
158 source_ranges: vec![args.source_range],
159 }));
160 }
161
162 let id = exec_state.next_uuid();
163 args.batch_modeling_cmd(
164 id,
165 ModelingCmd::from(mcmd::Loft {
166 section_ids: sketches.iter().map(|group| group.id).collect(),
167 base_curve_index,
168 bez_approximate_rational,
169 tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
170 v_degree,
171 }),
172 )
173 .await?;
174
175 let mut sketch = sketches[0].clone();
177 sketch.id = id;
179 Ok(Box::new(
180 do_post_extrude(
181 &sketch,
182 id.into(),
183 0.0,
184 &super::extrude::NamedCapTags {
185 start: tag_start.as_ref(),
186 end: tag_end.as_ref(),
187 },
188 exec_state,
189 &args,
190 )
191 .await?,
192 ))
193}