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 super::{args::TyF64, DEFAULT_TOLERANCE};
11use crate::{
12 errors::{KclError, KclErrorDetails},
13 execution::{types::RuntimeType, ExecState, KclValue, Sketch, Solid},
14 parsing::ast::types::TagNode,
15 std::{extrude::do_post_extrude, Args},
16};
17
18const DEFAULT_V_DEGREE: u32 = 2;
19
20pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
22 let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
23 let v_degree: NonZeroU32 = args
24 .get_kw_arg_opt("vDegree")?
25 .unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap());
26 let bez_approximate_rational = args.get_kw_arg_opt("bezApproximateRational")?.unwrap_or(false);
30 let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex")?;
32 let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::count(), exec_state)?;
34 let tag_start = args.get_kw_arg_opt("tagStart")?;
35 let tag_end = args.get_kw_arg_opt("tagEnd")?;
36
37 let value = inner_loft(
38 sketches,
39 v_degree,
40 bez_approximate_rational,
41 base_curve_index,
42 tolerance.map(|t| t.n),
43 tag_start,
44 tag_end,
45 exec_state,
46 args,
47 )
48 .await?;
49 Ok(KclValue::Solid { value })
50}
51
52#[stdlib {
119 name = "loft",
120 feature_tree_operation = true,
121 keywords = true,
122 unlabeled_first = true,
123 args = {
124 sketches = {docs = "Which sketches to loft. Must include at least 2 sketches."},
125 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."},
126 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."},
127 base_curve_index = {docs = "This can be set to override the automatically determined topological base curve, which is usually the first section encountered."},
128 tolerance = {docs = "Tolerance for the loft operation."},
129 tag_start = { docs = "A named tag for the face at the start of the loft, i.e. the original sketch" },
130 tag_end = { docs = "A named tag for the face at the end of the loft, i.e. the last sketch" },
131 }
132}]
133#[allow(clippy::too_many_arguments)]
134async fn inner_loft(
135 sketches: Vec<Sketch>,
136 v_degree: NonZeroU32,
137 bez_approximate_rational: bool,
138 base_curve_index: Option<u32>,
139 tolerance: Option<f64>,
140 tag_start: Option<TagNode>,
141 tag_end: Option<TagNode>,
142 exec_state: &mut ExecState,
143 args: Args,
144) -> Result<Box<Solid>, KclError> {
145 if sketches.len() < 2 {
147 return Err(KclError::Semantic(KclErrorDetails {
148 message: format!(
149 "Loft requires at least two sketches, but only {} were provided.",
150 sketches.len()
151 ),
152 source_ranges: vec![args.source_range],
153 }));
154 }
155
156 let id = exec_state.next_uuid();
157 args.batch_modeling_cmd(
158 id,
159 ModelingCmd::from(mcmd::Loft {
160 section_ids: sketches.iter().map(|group| group.id).collect(),
161 base_curve_index,
162 bez_approximate_rational,
163 tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
164 v_degree,
165 }),
166 )
167 .await?;
168
169 let mut sketch = sketches[0].clone();
171 sketch.id = id;
173 Ok(Box::new(
174 do_post_extrude(
175 &sketch,
176 id.into(),
177 0.0,
178 false,
179 &super::extrude::NamedCapTags {
180 start: tag_start.as_ref(),
181 end: tag_end.as_ref(),
182 },
183 exec_state,
184 &args,
185 )
186 .await?,
187 ))
188}