kcl_lib/std/
loft.rs

1//! Standard library lofts.
2
3use 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
22/// Create a 3D surface or solid by interpolating between two or more sketches.
23pub 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    // Attempt to approximate rational curves (such as arcs) using a bezier.
29    // This will remove banding around interpolations between arcs and non-arcs.  It may produce errors in other scenarios
30    // Over time, this field won't be necessary.
31    let bez_approximate_rational = args
32        .get_kw_arg_opt("bezApproximateRational", &RuntimeType::bool(), exec_state)?
33        .unwrap_or(false);
34    // This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
35    let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex", &RuntimeType::count(), exec_state)?;
36    // Tolerance for the loft operation.
37    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    // Make sure we have at least two sketches.
69    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    // Using the first sketch as the base curve, idk we might want to change this later.
94    let mut sketch = sketches[0].clone();
95    // Override its id with the loft id so we can get its faces later
96    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}