kcl_lib/std/
mod.rs

1//! Functions implemented for language execution.
2
3pub mod appearance;
4pub mod args;
5pub mod array;
6pub mod assert;
7pub mod axis_or_reference;
8pub mod chamfer;
9pub mod clone;
10pub mod csg;
11pub mod edge;
12pub mod extrude;
13pub mod fillet;
14pub mod helix;
15pub mod loft;
16pub mod math;
17pub mod mirror;
18pub mod patterns;
19pub mod planes;
20pub mod revolve;
21pub mod segment;
22pub mod shapes;
23pub mod shell;
24pub mod sketch;
25pub mod sweep;
26pub mod transform;
27pub mod utils;
28
29use anyhow::Result;
30pub use args::Args;
31use args::TyF64;
32use indexmap::IndexMap;
33use kcl_derive_docs::stdlib;
34use lazy_static::lazy_static;
35use parse_display::{Display, FromStr};
36use schemars::JsonSchema;
37use serde::{Deserialize, Serialize};
38
39use crate::{
40    docs::StdLibFn,
41    errors::KclError,
42    execution::{
43        types::{NumericType, PrimitiveType, RuntimeType, UnitAngle, UnitType},
44        ExecState, KclValue,
45    },
46    parsing::ast::types::Name,
47};
48
49pub type StdFn = fn(
50    &mut ExecState,
51    Args,
52) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<KclValue, KclError>> + Send + '_>>;
53
54lazy_static! {
55    static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![
56        Box::new(LegLen),
57        Box::new(LegAngX),
58        Box::new(LegAngY),
59        Box::new(crate::std::appearance::Appearance),
60        Box::new(crate::std::extrude::Extrude),
61        Box::new(crate::std::segment::SegEnd),
62        Box::new(crate::std::segment::SegEndX),
63        Box::new(crate::std::segment::SegEndY),
64        Box::new(crate::std::segment::SegStart),
65        Box::new(crate::std::segment::SegStartX),
66        Box::new(crate::std::segment::SegStartY),
67        Box::new(crate::std::segment::LastSegX),
68        Box::new(crate::std::segment::LastSegY),
69        Box::new(crate::std::segment::SegLen),
70        Box::new(crate::std::segment::SegAng),
71        Box::new(crate::std::segment::TangentToEnd),
72        Box::new(crate::std::shapes::CircleThreePoint),
73        Box::new(crate::std::shapes::Polygon),
74        Box::new(crate::std::sketch::InvoluteCircular),
75        Box::new(crate::std::sketch::Line),
76        Box::new(crate::std::sketch::XLine),
77        Box::new(crate::std::sketch::YLine),
78        Box::new(crate::std::sketch::AngledLine),
79        Box::new(crate::std::sketch::AngledLineThatIntersects),
80        Box::new(crate::std::sketch::StartSketchOn),
81        Box::new(crate::std::sketch::StartProfile),
82        Box::new(crate::std::sketch::ProfileStartX),
83        Box::new(crate::std::sketch::ProfileStartY),
84        Box::new(crate::std::sketch::ProfileStart),
85        Box::new(crate::std::sketch::Close),
86        Box::new(crate::std::sketch::Arc),
87        Box::new(crate::std::sketch::TangentialArc),
88        Box::new(crate::std::sketch::BezierCurve),
89        Box::new(crate::std::sketch::Subtract2D),
90        Box::new(crate::std::clone::Clone),
91        Box::new(crate::std::patterns::PatternLinear2D),
92        Box::new(crate::std::patterns::PatternLinear3D),
93        Box::new(crate::std::patterns::PatternCircular2D),
94        Box::new(crate::std::patterns::PatternCircular3D),
95        Box::new(crate::std::patterns::PatternTransform),
96        Box::new(crate::std::patterns::PatternTransform2D),
97        Box::new(crate::std::array::Reduce),
98        Box::new(crate::std::array::Map),
99        Box::new(crate::std::array::Push),
100        Box::new(crate::std::array::Pop),
101        Box::new(crate::std::edge::GetOppositeEdge),
102        Box::new(crate::std::edge::GetNextAdjacentEdge),
103        Box::new(crate::std::edge::GetPreviousAdjacentEdge),
104        Box::new(crate::std::edge::GetCommonEdge),
105        Box::new(crate::std::sweep::Sweep),
106        Box::new(crate::std::loft::Loft),
107        Box::new(crate::std::assert::Assert),
108        Box::new(crate::std::assert::AssertIs),
109        Box::new(crate::std::transform::Scale),
110        Box::new(crate::std::transform::Translate),
111        Box::new(crate::std::transform::Rotate),
112        Box::new(crate::std::csg::Intersect),
113        Box::new(crate::std::csg::Union),
114        Box::new(crate::std::csg::Subtract),
115    ];
116}
117
118pub fn name_in_stdlib(name: &str) -> bool {
119    CORE_FNS.iter().any(|f| f.name() == name)
120}
121
122pub fn get_stdlib_fn(name: &str) -> Option<Box<dyn StdLibFn>> {
123    CORE_FNS.iter().find(|f| f.name() == name).cloned()
124}
125
126#[derive(Clone, Debug, PartialEq, Eq)]
127pub struct StdFnProps {
128    pub name: String,
129    pub deprecated: bool,
130    pub include_in_feature_tree: bool,
131}
132
133impl StdFnProps {
134    fn default(name: &str) -> Self {
135        Self {
136            name: name.to_owned(),
137            deprecated: false,
138            include_in_feature_tree: false,
139        }
140    }
141
142    fn include_in_feature_tree(mut self) -> Self {
143        self.include_in_feature_tree = true;
144        self
145    }
146}
147
148pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProps) {
149    match (path, fn_name) {
150        ("math", "cos") => (
151            |e, a| Box::pin(crate::std::math::cos(e, a)),
152            StdFnProps::default("std::cos"),
153        ),
154        ("math", "sin") => (
155            |e, a| Box::pin(crate::std::math::sin(e, a)),
156            StdFnProps::default("std::sin"),
157        ),
158        ("math", "tan") => (
159            |e, a| Box::pin(crate::std::math::tan(e, a)),
160            StdFnProps::default("std::tan"),
161        ),
162        ("math", "acos") => (
163            |e, a| Box::pin(crate::std::math::acos(e, a)),
164            StdFnProps::default("std::acos"),
165        ),
166        ("math", "asin") => (
167            |e, a| Box::pin(crate::std::math::asin(e, a)),
168            StdFnProps::default("std::asin"),
169        ),
170        ("math", "atan") => (
171            |e, a| Box::pin(crate::std::math::atan(e, a)),
172            StdFnProps::default("std::atan"),
173        ),
174        ("math", "atan2") => (
175            |e, a| Box::pin(crate::std::math::atan2(e, a)),
176            StdFnProps::default("std::atan2"),
177        ),
178        ("math", "sqrt") => (
179            |e, a| Box::pin(crate::std::math::sqrt(e, a)),
180            StdFnProps::default("std::sqrt"),
181        ),
182
183        ("math", "abs") => (
184            |e, a| Box::pin(crate::std::math::abs(e, a)),
185            StdFnProps::default("std::abs"),
186        ),
187        ("math", "rem") => (
188            |e, a| Box::pin(crate::std::math::rem(e, a)),
189            StdFnProps::default("std::rem"),
190        ),
191        ("math", "round") => (
192            |e, a| Box::pin(crate::std::math::round(e, a)),
193            StdFnProps::default("std::round"),
194        ),
195        ("math", "floor") => (
196            |e, a| Box::pin(crate::std::math::floor(e, a)),
197            StdFnProps::default("std::floor"),
198        ),
199        ("math", "ceil") => (
200            |e, a| Box::pin(crate::std::math::ceil(e, a)),
201            StdFnProps::default("std::ceil"),
202        ),
203        ("math", "min") => (
204            |e, a| Box::pin(crate::std::math::min(e, a)),
205            StdFnProps::default("std::min"),
206        ),
207        ("math", "max") => (
208            |e, a| Box::pin(crate::std::math::max(e, a)),
209            StdFnProps::default("std::max"),
210        ),
211        ("math", "pow") => (
212            |e, a| Box::pin(crate::std::math::pow(e, a)),
213            StdFnProps::default("std::pow"),
214        ),
215        ("math", "log") => (
216            |e, a| Box::pin(crate::std::math::log(e, a)),
217            StdFnProps::default("std::log"),
218        ),
219        ("math", "log2") => (
220            |e, a| Box::pin(crate::std::math::log2(e, a)),
221            StdFnProps::default("std::log2"),
222        ),
223        ("math", "log10") => (
224            |e, a| Box::pin(crate::std::math::log10(e, a)),
225            StdFnProps::default("std::log10"),
226        ),
227        ("math", "ln") => (
228            |e, a| Box::pin(crate::std::math::ln(e, a)),
229            StdFnProps::default("std::ln"),
230        ),
231        ("sketch", "circle") => (
232            |e, a| Box::pin(crate::std::shapes::circle(e, a)),
233            StdFnProps::default("std::sketch::circle"),
234        ),
235        ("prelude", "helix") => (
236            |e, a| Box::pin(crate::std::helix::helix(e, a)),
237            StdFnProps::default("std::helix").include_in_feature_tree(),
238        ),
239        ("transform", "mirror2d") => (
240            |e, a| Box::pin(crate::std::mirror::mirror_2d(e, a)),
241            StdFnProps::default("std::transform::mirror2d"),
242        ),
243        ("sketch", "revolve") => (
244            |e, a| Box::pin(crate::std::revolve::revolve(e, a)),
245            StdFnProps::default("std::sketch::revolve").include_in_feature_tree(),
246        ),
247        ("prelude", "offsetPlane") => (
248            |e, a| Box::pin(crate::std::planes::offset_plane(e, a)),
249            StdFnProps::default("std::offsetPlane").include_in_feature_tree(),
250        ),
251        ("solid", "fillet") => (
252            |e, a| Box::pin(crate::std::fillet::fillet(e, a)),
253            StdFnProps::default("std::solid::fillet").include_in_feature_tree(),
254        ),
255        ("solid", "chamfer") => (
256            |e, a| Box::pin(crate::std::chamfer::chamfer(e, a)),
257            StdFnProps::default("std::solid::chamfer").include_in_feature_tree(),
258        ),
259        ("solid", "shell") => (
260            |e, a| Box::pin(crate::std::shell::shell(e, a)),
261            StdFnProps::default("std::solid::shell").include_in_feature_tree(),
262        ),
263        ("solid", "hollow") => (
264            |e, a| Box::pin(crate::std::shell::hollow(e, a)),
265            StdFnProps::default("std::solid::hollow").include_in_feature_tree(),
266        ),
267        _ => unreachable!(),
268    }
269}
270
271pub(crate) fn std_ty(path: &str, fn_name: &str) -> (PrimitiveType, StdFnProps) {
272    match (path, fn_name) {
273        ("types", "Sketch") => (PrimitiveType::Sketch, StdFnProps::default("std::types::Sketch")),
274        ("types", "Solid") => (PrimitiveType::Solid, StdFnProps::default("std::types::Solid")),
275        ("types", "Plane") => (PrimitiveType::Plane, StdFnProps::default("std::types::Plane")),
276        ("types", "Face") => (PrimitiveType::Face, StdFnProps::default("std::types::Face")),
277        ("types", "Helix") => (PrimitiveType::Helix, StdFnProps::default("std::types::Helix")),
278        ("types", "Edge") => (PrimitiveType::Edge, StdFnProps::default("std::types::Edge")),
279        ("types", "Axis2d") => (PrimitiveType::Axis2d, StdFnProps::default("std::types::Axis2d")),
280        ("types", "Axis3d") => (PrimitiveType::Axis3d, StdFnProps::default("std::types::Axis3d")),
281        _ => unreachable!(),
282    }
283}
284
285pub struct StdLib {
286    pub fns: IndexMap<String, Box<dyn StdLibFn>>,
287}
288
289impl std::fmt::Debug for StdLib {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        f.debug_struct("StdLib").field("fns.len()", &self.fns.len()).finish()
292    }
293}
294
295impl StdLib {
296    pub fn new() -> Self {
297        let fns = CORE_FNS
298            .clone()
299            .into_iter()
300            .map(|internal_fn| (internal_fn.name(), internal_fn))
301            .collect();
302
303        Self { fns }
304    }
305
306    // Get the combined hashmaps.
307    pub fn combined(&self) -> IndexMap<String, Box<dyn StdLibFn>> {
308        self.fns.clone()
309    }
310
311    pub fn get(&self, name: &str) -> Option<Box<dyn StdLibFn>> {
312        self.fns.get(name).cloned()
313    }
314
315    pub fn get_either(&self, name: &Name) -> FunctionKind {
316        if let Some(name) = name.local_ident() {
317            if let Some(f) = self.get(name.inner) {
318                return FunctionKind::Core(f);
319            }
320        }
321
322        FunctionKind::UserDefined
323    }
324
325    pub fn contains_key(&self, key: &str) -> bool {
326        self.fns.contains_key(key)
327    }
328}
329
330impl Default for StdLib {
331    fn default() -> Self {
332        Self::new()
333    }
334}
335
336#[derive(Debug)]
337pub enum FunctionKind {
338    Core(Box<dyn StdLibFn>),
339    UserDefined,
340}
341
342/// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`].
343const DEFAULT_TOLERANCE: f64 = 0.0000001;
344
345/// Compute the length of the given leg.
346pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
347    let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
348    let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
349    let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
350    let result = inner_leg_length(hypotenuse, leg);
351    Ok(KclValue::from_number_with_type(result, ty, vec![args.into()]))
352}
353
354/// Compute the length of the given leg.
355///
356/// ```kcl,no_run
357/// legLen(hypotenuse = 5, leg = 3)
358/// ```
359#[stdlib {
360    name = "legLen",
361    keywords = true,
362    unlabeled_first = false,
363    args = {
364        hypotenuse = { docs = "The length of the triangle's hypotenuse" },
365        leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" },
366    },
367    tags = ["math"],
368}]
369fn inner_leg_length(hypotenuse: f64, leg: f64) -> f64 {
370    (hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt()
371}
372
373/// Compute the angle of the given leg for x.
374pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
375    let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
376    let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
377    let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
378    let result = inner_leg_angle_x(hypotenuse, leg);
379    Ok(KclValue::from_number_with_type(
380        result,
381        NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
382        vec![args.into()],
383    ))
384}
385
386/// Compute the angle of the given leg for x.
387///
388/// ```kcl,no_run
389/// legAngX(hypotenuse = 5, leg = 3)
390/// ```
391#[stdlib {
392    name = "legAngX",
393    keywords = true,
394    unlabeled_first = false,
395    args = {
396        hypotenuse = { docs = "The length of the triangle's hypotenuse" },
397        leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" },
398    },
399    tags = ["math"],
400}]
401fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 {
402    (leg.min(hypotenuse) / hypotenuse).acos().to_degrees()
403}
404
405/// Compute the angle of the given leg for y.
406pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
407    let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
408    let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
409    let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
410    let result = inner_leg_angle_y(hypotenuse, leg);
411    Ok(KclValue::from_number_with_type(
412        result,
413        NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
414        vec![args.into()],
415    ))
416}
417
418/// Compute the angle of the given leg for y.
419///
420/// ```kcl,no_run
421/// legAngY(hypotenuse = 5, leg = 3)
422/// ```
423#[stdlib {
424    name = "legAngY",
425    keywords = true,
426    unlabeled_first = false,
427    args = {
428        hypotenuse = { docs = "The length of the triangle's hypotenuse" },
429        leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" },
430    },
431    tags = ["math"],
432}]
433fn inner_leg_angle_y(hypotenuse: f64, leg: f64) -> f64 {
434    (leg.min(hypotenuse) / hypotenuse).asin().to_degrees()
435}
436
437/// The primitive types that can be used in a KCL file.
438#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr)]
439#[serde(rename_all = "lowercase")]
440#[display(style = "lowercase")]
441pub enum Primitive {
442    /// A boolean value.
443    Bool,
444    /// A number value.
445    Number,
446    /// A string value.
447    String,
448    /// A uuid value.
449    Uuid,
450}