kcl_lib/std/
args.rs

1use std::num::NonZeroU32;
2
3use anyhow::Result;
4use kittycad_modeling_cmds::units::{UnitAngle, UnitLength};
5use serde::Serialize;
6
7use super::fillet::EdgeReference;
8pub use crate::execution::fn_call::Args;
9use crate::{
10    CompilationError, ModuleId, SourceRange,
11    errors::{KclError, KclErrorDetails},
12    execution::{
13        ExecState, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Plane, PlaneInfo, Sketch, SketchSurface,
14        Solid, TagIdentifier, annotations,
15        kcl_value::FunctionSource,
16        types::{NumericType, PrimitiveType, RuntimeType, UnitType},
17    },
18    parsing::ast::types::TagNode,
19    std::{
20        shapes::{PolygonType, SketchOrSurface},
21        sketch::FaceTag,
22        sweep::SweepPath,
23    },
24};
25
26const ERROR_STRING_SKETCH_TO_SOLID_HELPER: &str =
27    "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`";
28
29#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
30#[ts(export)]
31#[serde(rename_all = "camelCase")]
32pub struct TyF64 {
33    pub n: f64,
34    pub ty: NumericType,
35}
36
37impl TyF64 {
38    pub const fn new(n: f64, ty: NumericType) -> Self {
39        Self { n, ty }
40    }
41
42    pub fn to_mm(&self) -> f64 {
43        self.to_length_units(UnitLength::Millimeters)
44    }
45
46    pub fn to_length_units(&self, units: UnitLength) -> f64 {
47        let len = match &self.ty {
48            NumericType::Default { len, .. } => *len,
49            NumericType::Known(UnitType::Length(len)) => *len,
50            t => unreachable!("expected length, found {t:?}"),
51        };
52
53        crate::execution::types::adjust_length(len, self.n, units).0
54    }
55
56    pub fn to_degrees(&self, exec_state: &mut ExecState, source_range: SourceRange) -> f64 {
57        let angle = match self.ty {
58            NumericType::Default { angle, .. } => {
59                if self.n != 0.0 {
60                    exec_state.warn(
61                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
62                        annotations::WARN_ANGLE_UNITS,
63                    );
64                }
65                angle
66            }
67            NumericType::Known(UnitType::Angle(angle)) => angle,
68            _ => unreachable!(),
69        };
70
71        crate::execution::types::adjust_angle(angle, self.n, UnitAngle::Degrees).0
72    }
73
74    pub fn to_radians(&self, exec_state: &mut ExecState, source_range: SourceRange) -> f64 {
75        let angle = match self.ty {
76            NumericType::Default { angle, .. } => {
77                if self.n != 0.0 {
78                    exec_state.warn(
79                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
80                        annotations::WARN_ANGLE_UNITS,
81                    );
82                }
83                angle
84            }
85            NumericType::Known(UnitType::Angle(angle)) => angle,
86            _ => unreachable!(),
87        };
88
89        crate::execution::types::adjust_angle(angle, self.n, UnitAngle::Radians).0
90    }
91    pub fn count(n: f64) -> Self {
92        Self {
93            n,
94            ty: NumericType::count(),
95        }
96    }
97
98    pub fn map_value(mut self, n: f64) -> Self {
99        self.n = n;
100        self
101    }
102}
103
104impl Args {
105    pub(crate) fn get_kw_arg_opt<T>(
106        &self,
107        label: &str,
108        ty: &RuntimeType,
109        exec_state: &mut ExecState,
110    ) -> Result<Option<T>, KclError>
111    where
112        T: for<'a> FromKclValue<'a>,
113    {
114        match self.labeled.get(label) {
115            None => return Ok(None),
116            Some(a) => {
117                if let KclValue::KclNone { .. } = &a.value {
118                    return Ok(None);
119                }
120            }
121        }
122
123        self.get_kw_arg(label, ty, exec_state).map(Some)
124    }
125
126    pub(crate) fn get_kw_arg<T>(&self, label: &str, ty: &RuntimeType, exec_state: &mut ExecState) -> Result<T, KclError>
127    where
128        T: for<'a> FromKclValue<'a>,
129    {
130        let Some(arg) = self.labeled.get(label) else {
131            return Err(KclError::new_semantic(KclErrorDetails::new(
132                if let Some(ref fname) = self.fn_name {
133                    format!("The `{fname}` function requires a keyword argument `{label}`")
134                } else {
135                    format!("This function requires a keyword argument `{label}`")
136                },
137                vec![self.source_range],
138            )));
139        };
140
141        let arg = arg.value.coerce(ty, true, exec_state).map_err(|_| {
142            let actual_type = arg.value.principal_type();
143            let actual_type_name = actual_type
144                .as_ref()
145                .map(|t| t.to_string())
146                .unwrap_or_else(|| arg.value.human_friendly_type());
147            let msg_base = if let Some(ref fname) = self.fn_name {
148                format!("The `{fname}` function expected its `{label}` argument to be {} but it's actually of type {actual_type_name}", ty.human_friendly_type())
149            } else {
150                format!("This function expected its `{label}` argument to be {} but it's actually of type {actual_type_name}", ty.human_friendly_type())
151            };
152            let suggestion = match (ty, actual_type) {
153                (RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
154                    Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
155                }
156                (RuntimeType::Array(t, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
157                    if **t == RuntimeType::Primitive(PrimitiveType::Solid) =>
158                {
159                    Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
160                }
161                _ => None,
162            };
163            let mut message = match suggestion {
164                None => msg_base,
165                Some(sugg) => format!("{msg_base}. {sugg}"),
166            };
167            if message.contains("one or more Solids or ImportedGeometry but it's actually of type Sketch") {
168                message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
169            }
170            KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
171        })?;
172
173        T::from_kcl_val(&arg).ok_or_else(|| {
174            KclError::new_internal(KclErrorDetails::new(
175                format!("Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}"),
176                vec![self.source_range],
177           ))
178        })
179    }
180
181    /// Get a labelled keyword arg, check it's an array, and return all items in the array
182    /// plus their source range.
183    pub(crate) fn kw_arg_edge_array_and_source(
184        &self,
185        label: &str,
186    ) -> Result<Vec<(EdgeReference, SourceRange)>, KclError> {
187        let Some(arg) = self.labeled.get(label) else {
188            let err = KclError::new_semantic(KclErrorDetails::new(
189                if let Some(ref fname) = self.fn_name {
190                    format!("The `{fname}` function requires a keyword argument '{label}'")
191                } else {
192                    format!("This function requires a keyword argument '{label}'")
193                },
194                vec![self.source_range],
195            ));
196            return Err(err);
197        };
198        arg.value
199            .clone()
200            .into_array()
201            .iter()
202            .map(|item| {
203                let source = SourceRange::from(item);
204                let val = FromKclValue::from_kcl_val(item).ok_or_else(|| {
205                    KclError::new_semantic(KclErrorDetails::new(
206                        format!("Expected an Edge but found {}", arg.value.human_friendly_type()),
207                        arg.source_ranges(),
208                    ))
209                })?;
210                Ok((val, source))
211            })
212            .collect::<Result<Vec<_>, _>>()
213    }
214
215    pub(crate) fn get_unlabeled_kw_arg_array_and_type(
216        &self,
217        label: &str,
218        exec_state: &mut ExecState,
219    ) -> Result<(Vec<KclValue>, RuntimeType), KclError> {
220        let value = self.get_unlabeled_kw_arg(label, &RuntimeType::any_array(), exec_state)?;
221        Ok(match value {
222            KclValue::HomArray { value, ty } => (value, ty),
223            KclValue::Tuple { value, .. } => (value, RuntimeType::any()),
224            val => (vec![val], RuntimeType::any()),
225        })
226    }
227
228    /// Get the unlabeled keyword argument. If not set, returns Err. If it
229    /// can't be converted to the given type, returns Err.
230    pub(crate) fn get_unlabeled_kw_arg<T>(
231        &self,
232        label: &str,
233        ty: &RuntimeType,
234        exec_state: &mut ExecState,
235    ) -> Result<T, KclError>
236    where
237        T: for<'a> FromKclValue<'a>,
238    {
239        let arg = self
240            .unlabeled_kw_arg_unconverted()
241            .ok_or(KclError::new_semantic(KclErrorDetails::new(
242                if let Some(ref fname) = self.fn_name {
243                    format!(
244                        "The `{fname}` function requires a value for the special unlabeled first parameter, '{label}'"
245                    )
246                } else {
247                    format!("This function requires a value for the special unlabeled first parameter, '{label}'")
248                },
249                vec![self.source_range],
250            )))?;
251
252        let arg = arg.value.coerce(ty, true, exec_state).map_err(|_| {
253            let actual_type = arg.value.principal_type();
254            let actual_type_name = actual_type
255                .as_ref()
256                .map(|t| t.to_string())
257                .unwrap_or_else(|| arg.value.human_friendly_type());
258            let msg_base = if let Some(ref fname) = self.fn_name {
259                format!(
260                    "The `{fname}` function expected the input argument to be {} but it's actually of type {actual_type_name}",
261                    ty.human_friendly_type(),
262                )
263            } else {
264                format!(
265                    "This function expected the input argument to be {} but it's actually of type {actual_type_name}",
266                    ty.human_friendly_type(),
267                )
268            };
269            let suggestion = match (ty, actual_type) {
270                (RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
271                    Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
272                }
273                (RuntimeType::Array(ty, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
274                    if **ty == RuntimeType::Primitive(PrimitiveType::Solid) =>
275                {
276                    Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
277                }
278                _ => None,
279            };
280            let mut message = match suggestion {
281                None => msg_base,
282                Some(sugg) => format!("{msg_base}. {sugg}"),
283            };
284
285            if message.contains("one or more Solids or ImportedGeometry but it's actually of type Sketch") {
286                message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
287            }
288            KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
289        })?;
290
291        T::from_kcl_val(&arg).ok_or_else(|| {
292            KclError::new_internal(KclErrorDetails::new(
293                format!("Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}"),
294                vec![self.source_range],
295           ))
296        })
297    }
298
299    // TODO: Move this to the modeling module.
300    fn get_tag_info_from_memory<'a, 'e>(
301        &'a self,
302        exec_state: &'e mut ExecState,
303        tag: &'a TagIdentifier,
304    ) -> Result<&'e crate::execution::TagEngineInfo, KclError> {
305        if let (epoch, KclValue::TagIdentifier(t)) =
306            exec_state.stack().get_from_call_stack(&tag.value, self.source_range)?
307        {
308            let info = t.get_info(epoch).ok_or_else(|| {
309                KclError::new_type(KclErrorDetails::new(
310                    format!("Tag `{}` does not have engine info", tag.value),
311                    vec![self.source_range],
312                ))
313            })?;
314            Ok(info)
315        } else {
316            Err(KclError::new_type(KclErrorDetails::new(
317                format!("Tag `{}` does not exist", tag.value),
318                vec![self.source_range],
319            )))
320        }
321    }
322
323    // TODO: Move this to the modeling module.
324    pub(crate) fn get_tag_engine_info<'a, 'e>(
325        &'a self,
326        exec_state: &'e mut ExecState,
327        tag: &'a TagIdentifier,
328    ) -> Result<&'a crate::execution::TagEngineInfo, KclError>
329    where
330        'e: 'a,
331    {
332        if let Some(info) = tag.get_cur_info() {
333            return Ok(info);
334        }
335
336        self.get_tag_info_from_memory(exec_state, tag)
337    }
338
339    // TODO: Move this to the modeling module.
340    fn get_tag_engine_info_check_surface<'a, 'e>(
341        &'a self,
342        exec_state: &'e mut ExecState,
343        tag: &'a TagIdentifier,
344    ) -> Result<&'a crate::execution::TagEngineInfo, KclError>
345    where
346        'e: 'a,
347    {
348        if let Some(info) = tag.get_cur_info()
349            && info.surface.is_some()
350        {
351            return Ok(info);
352        }
353
354        self.get_tag_info_from_memory(exec_state, tag)
355    }
356
357    pub(crate) fn make_kcl_val_from_point(&self, p: [f64; 2], ty: NumericType) -> Result<KclValue, KclError> {
358        let meta = Metadata {
359            source_range: self.source_range,
360        };
361        let x = KclValue::Number {
362            value: p[0],
363            meta: vec![meta],
364            ty,
365        };
366        let y = KclValue::Number {
367            value: p[1],
368            meta: vec![meta],
369            ty,
370        };
371        let ty = RuntimeType::Primitive(PrimitiveType::Number(ty));
372
373        Ok(KclValue::HomArray { value: vec![x, y], ty })
374    }
375
376    pub(super) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
377        KclValue::from_number_with_type(
378            f.n,
379            f.ty,
380            vec![Metadata {
381                source_range: self.source_range,
382            }],
383        )
384    }
385
386    // TODO: Move this to the modeling module.
387    pub(crate) async fn get_adjacent_face_to_tag(
388        &self,
389        exec_state: &mut ExecState,
390        tag: &TagIdentifier,
391        must_be_planar: bool,
392    ) -> Result<uuid::Uuid, KclError> {
393        if tag.value.is_empty() {
394            return Err(KclError::new_type(KclErrorDetails::new(
395                "Expected a non-empty tag for the face".to_string(),
396                vec![self.source_range],
397            )));
398        }
399
400        let engine_info = self.get_tag_engine_info_check_surface(exec_state, tag)?;
401
402        let surface = engine_info.surface.as_ref().ok_or_else(|| {
403            KclError::new_type(KclErrorDetails::new(
404                format!("Tag `{}` does not have a surface", tag.value),
405                vec![self.source_range],
406            ))
407        })?;
408
409        if let Some(face_from_surface) = match surface {
410            ExtrudeSurface::ExtrudePlane(extrude_plane) => {
411                if let Some(plane_tag) = &extrude_plane.tag {
412                    if plane_tag.name == tag.value {
413                        Some(Ok(extrude_plane.face_id))
414                    } else {
415                        None
416                    }
417                } else {
418                    None
419                }
420            }
421            // The must be planar check must be called before the arc check.
422            ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::new_type(KclErrorDetails::new(
423                format!("Tag `{}` is a non-planar surface", tag.value),
424                vec![self.source_range],
425            )))),
426            ExtrudeSurface::ExtrudeArc(extrude_arc) => {
427                if let Some(arc_tag) = &extrude_arc.tag {
428                    if arc_tag.name == tag.value {
429                        Some(Ok(extrude_arc.face_id))
430                    } else {
431                        None
432                    }
433                } else {
434                    None
435                }
436            }
437            ExtrudeSurface::Chamfer(chamfer) => {
438                if let Some(chamfer_tag) = &chamfer.tag {
439                    if chamfer_tag.name == tag.value {
440                        Some(Ok(chamfer.face_id))
441                    } else {
442                        None
443                    }
444                } else {
445                    None
446                }
447            }
448            // The must be planar check must be called before the fillet check.
449            ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::new_type(KclErrorDetails::new(
450                format!("Tag `{}` is a non-planar surface", tag.value),
451                vec![self.source_range],
452            )))),
453            ExtrudeSurface::Fillet(fillet) => {
454                if let Some(fillet_tag) = &fillet.tag {
455                    if fillet_tag.name == tag.value {
456                        Some(Ok(fillet.face_id))
457                    } else {
458                        None
459                    }
460                } else {
461                    None
462                }
463            }
464        } {
465            return face_from_surface;
466        }
467
468        // If we still haven't found the face, return an error.
469        Err(KclError::new_type(KclErrorDetails::new(
470            format!("Expected a face with the tag `{}`", tag.value),
471            vec![self.source_range],
472        )))
473    }
474}
475
476/// Types which impl this trait can be extracted from a `KclValue`.
477pub trait FromKclValue<'a>: Sized {
478    /// Try to convert a KclValue into this type.
479    fn from_kcl_val(arg: &'a KclValue) -> Option<Self>;
480}
481
482impl<'a> FromKclValue<'a> for TagNode {
483    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
484        arg.get_tag_declarator().ok()
485    }
486}
487
488impl<'a> FromKclValue<'a> for TagIdentifier {
489    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
490        arg.get_tag_identifier().ok()
491    }
492}
493
494impl<'a> FromKclValue<'a> for Vec<TagIdentifier> {
495    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
496        let tags = arg
497            .clone()
498            .into_array()
499            .iter()
500            .map(|v| v.get_tag_identifier().unwrap())
501            .collect();
502        Some(tags)
503    }
504}
505
506impl<'a> FromKclValue<'a> for Vec<KclValue> {
507    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
508        Some(arg.clone().into_array())
509    }
510}
511
512impl<'a> FromKclValue<'a> for KclValue {
513    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
514        Some(arg.clone())
515    }
516}
517
518macro_rules! let_field_of {
519    // Optional field
520    ($obj:ident, $field:ident?) => {
521        let $field = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val);
522    };
523    // Optional field but with a different string used as the key
524    ($obj:ident, $field:ident? $key:literal) => {
525        let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val);
526    };
527    // Mandatory field, but with a different string used as the key.
528    ($obj:ident, $field:ident $key:literal) => {
529        let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val)?;
530    };
531    // Mandatory field, optionally with a type annotation
532    ($obj:ident, $field:ident $(, $annotation:ty)?) => {
533        let $field $(: $annotation)? = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val)?;
534    };
535}
536
537impl<'a> FromKclValue<'a> for crate::execution::Plane {
538    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
539        arg.as_plane().cloned()
540    }
541}
542
543impl<'a> FromKclValue<'a> for crate::execution::PlaneType {
544    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
545        let plane_type = match arg.as_str()? {
546            "XY" | "xy" => Self::XY,
547            "XZ" | "xz" => Self::XZ,
548            "YZ" | "yz" => Self::YZ,
549            "Custom" => Self::Custom,
550            _ => return None,
551        };
552        Some(plane_type)
553    }
554}
555
556impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::units::UnitLength {
557    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
558        let s = arg.as_str()?;
559        s.parse().ok()
560    }
561}
562
563impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::System {
564    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
565        let obj = arg.as_object()?;
566        let_field_of!(obj, forward);
567        let_field_of!(obj, up);
568        Some(Self { forward, up })
569    }
570}
571
572impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::AxisDirectionPair {
573    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
574        let obj = arg.as_object()?;
575        let_field_of!(obj, axis);
576        let_field_of!(obj, direction);
577        Some(Self { axis, direction })
578    }
579}
580
581impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Axis {
582    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
583        let s = arg.as_str()?;
584        match s {
585            "y" => Some(Self::Y),
586            "z" => Some(Self::Z),
587            _ => None,
588        }
589    }
590}
591
592impl<'a> FromKclValue<'a> for PolygonType {
593    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
594        let s = arg.as_str()?;
595        match s {
596            "inscribed" => Some(Self::Inscribed),
597            _ => Some(Self::Circumscribed),
598        }
599    }
600}
601
602impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Direction {
603    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
604        let s = arg.as_str()?;
605        match s {
606            "positive" => Some(Self::Positive),
607            "negative" => Some(Self::Negative),
608            _ => None,
609        }
610    }
611}
612
613impl<'a> FromKclValue<'a> for crate::execution::Geometry {
614    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
615        match arg {
616            KclValue::Sketch { value } => Some(Self::Sketch(*value.to_owned())),
617            KclValue::Solid { value } => Some(Self::Solid(*value.to_owned())),
618            _ => None,
619        }
620    }
621}
622
623impl<'a> FromKclValue<'a> for crate::execution::GeometryWithImportedGeometry {
624    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
625        match arg {
626            KclValue::Sketch { value } => Some(Self::Sketch(*value.to_owned())),
627            KclValue::Solid { value } => Some(Self::Solid(*value.to_owned())),
628            KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
629            _ => None,
630        }
631    }
632}
633
634impl<'a> FromKclValue<'a> for FaceTag {
635    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
636        let case1 = || match arg.as_str() {
637            Some("start" | "START") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::Start)),
638            Some("end" | "END") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::End)),
639            _ => None,
640        };
641        let case2 = || {
642            let tag = TagIdentifier::from_kcl_val(arg)?;
643            Some(Self::Tag(Box::new(tag)))
644        };
645        case1().or_else(case2)
646    }
647}
648
649impl<'a> FromKclValue<'a> for super::sketch::TangentialArcData {
650    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
651        let obj = arg.as_object()?;
652        let_field_of!(obj, radius);
653        let_field_of!(obj, offset);
654        Some(Self::RadiusAndOffset { radius, offset })
655    }
656}
657
658impl<'a> FromKclValue<'a> for crate::execution::Point3d {
659    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
660        // Case 1: object with x/y/z fields
661        if let Some(obj) = arg.as_object() {
662            let_field_of!(obj, x, TyF64);
663            let_field_of!(obj, y, TyF64);
664            let_field_of!(obj, z, TyF64);
665            // TODO here and below we could use coercing combination.
666            let (a, ty) = NumericType::combine_eq_array(&[x, y, z]);
667            return Some(Self {
668                x: a[0],
669                y: a[1],
670                z: a[2],
671                units: ty.as_length(),
672            });
673        }
674        // Case 2: Array of 3 numbers.
675        let [x, y, z]: [TyF64; 3] = FromKclValue::from_kcl_val(arg)?;
676        let (a, ty) = NumericType::combine_eq_array(&[x, y, z]);
677        Some(Self {
678            x: a[0],
679            y: a[1],
680            z: a[2],
681            units: ty.as_length(),
682        })
683    }
684}
685
686impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
687    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
688        // Case 0: actual plane
689        if let KclValue::Plane { value } = arg {
690            return Some(Self::Plane(PlaneInfo {
691                origin: value.info.origin,
692                x_axis: value.info.x_axis,
693                y_axis: value.info.y_axis,
694                z_axis: value.info.z_axis,
695            }));
696        }
697        // Case 1: predefined plane
698        if let Some(s) = arg.as_str() {
699            return match s {
700                "XY" | "xy" => Some(Self::XY),
701                "-XY" | "-xy" => Some(Self::NegXY),
702                "XZ" | "xz" => Some(Self::XZ),
703                "-XZ" | "-xz" => Some(Self::NegXZ),
704                "YZ" | "yz" => Some(Self::YZ),
705                "-YZ" | "-yz" => Some(Self::NegYZ),
706                _ => None,
707            };
708        }
709        // Case 2: custom plane
710        let obj = arg.as_object()?;
711        let_field_of!(obj, plane, &KclObjectFields);
712        let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val)?;
713        let x_axis: crate::execution::Point3d = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?;
714        let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val)?;
715        let z_axis = x_axis.axes_cross_product(&y_axis);
716        Some(Self::Plane(PlaneInfo {
717            origin,
718            x_axis,
719            y_axis,
720            z_axis,
721        }))
722    }
723}
724
725impl<'a> FromKclValue<'a> for crate::execution::ExtrudePlane {
726    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
727        let obj = arg.as_object()?;
728        let_field_of!(obj, face_id "faceId");
729        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
730        let_field_of!(obj, geo_meta "geoMeta");
731        Some(Self { face_id, tag, geo_meta })
732    }
733}
734
735impl<'a> FromKclValue<'a> for crate::execution::ExtrudeArc {
736    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
737        let obj = arg.as_object()?;
738        let_field_of!(obj, face_id "faceId");
739        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
740        let_field_of!(obj, geo_meta "geoMeta");
741        Some(Self { face_id, tag, geo_meta })
742    }
743}
744
745impl<'a> FromKclValue<'a> for crate::execution::GeoMeta {
746    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
747        let obj = arg.as_object()?;
748        let_field_of!(obj, id);
749        let_field_of!(obj, source_range "sourceRange");
750        Some(Self {
751            id,
752            metadata: Metadata { source_range },
753        })
754    }
755}
756
757impl<'a> FromKclValue<'a> for crate::execution::ChamferSurface {
758    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
759        let obj = arg.as_object()?;
760        let_field_of!(obj, face_id "faceId");
761        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
762        let_field_of!(obj, geo_meta "geoMeta");
763        Some(Self { face_id, tag, geo_meta })
764    }
765}
766
767impl<'a> FromKclValue<'a> for crate::execution::FilletSurface {
768    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
769        let obj = arg.as_object()?;
770        let_field_of!(obj, face_id "faceId");
771        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
772        let_field_of!(obj, geo_meta "geoMeta");
773        Some(Self { face_id, tag, geo_meta })
774    }
775}
776
777impl<'a> FromKclValue<'a> for ExtrudeSurface {
778    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
779        let case1 = crate::execution::ExtrudePlane::from_kcl_val;
780        let case2 = crate::execution::ExtrudeArc::from_kcl_val;
781        let case3 = crate::execution::ChamferSurface::from_kcl_val;
782        let case4 = crate::execution::FilletSurface::from_kcl_val;
783        case1(arg)
784            .map(Self::ExtrudePlane)
785            .or_else(|| case2(arg).map(Self::ExtrudeArc))
786            .or_else(|| case3(arg).map(Self::Chamfer))
787            .or_else(|| case4(arg).map(Self::Fillet))
788    }
789}
790
791impl<'a> FromKclValue<'a> for crate::execution::EdgeCut {
792    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
793        let obj = arg.as_object()?;
794        let_field_of!(obj, typ "type");
795        let tag = Box::new(obj.get("tag").and_then(FromKclValue::from_kcl_val));
796        let_field_of!(obj, edge_id "edgeId");
797        let_field_of!(obj, id);
798        match typ {
799            "fillet" => {
800                let_field_of!(obj, radius);
801                Some(Self::Fillet {
802                    edge_id,
803                    tag,
804                    id,
805                    radius,
806                })
807            }
808            "chamfer" => {
809                let_field_of!(obj, length);
810                Some(Self::Chamfer {
811                    id,
812                    length,
813                    edge_id,
814                    tag,
815                })
816            }
817            _ => None,
818        }
819    }
820}
821
822macro_rules! impl_from_kcl_for_vec {
823    ($typ:path) => {
824        impl<'a> FromKclValue<'a> for Vec<$typ> {
825            fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
826                arg.clone()
827                    .into_array()
828                    .iter()
829                    .map(|value| FromKclValue::from_kcl_val(value))
830                    .collect::<Option<_>>()
831            }
832        }
833    };
834}
835
836impl_from_kcl_for_vec!(FaceTag);
837impl_from_kcl_for_vec!(crate::execution::EdgeCut);
838impl_from_kcl_for_vec!(crate::execution::Metadata);
839impl_from_kcl_for_vec!(super::fillet::EdgeReference);
840impl_from_kcl_for_vec!(ExtrudeSurface);
841impl_from_kcl_for_vec!(TyF64);
842impl_from_kcl_for_vec!(Solid);
843impl_from_kcl_for_vec!(Sketch);
844
845impl<'a> FromKclValue<'a> for SourceRange {
846    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
847        let value = match arg {
848            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
849            _ => {
850                return None;
851            }
852        };
853        if value.len() != 3 {
854            return None;
855        }
856        let v0 = value.first()?;
857        let v1 = value.get(1)?;
858        let v2 = value.get(2)?;
859        Some(SourceRange::new(
860            v0.as_usize()?,
861            v1.as_usize()?,
862            ModuleId::from_usize(v2.as_usize()?),
863        ))
864    }
865}
866
867impl<'a> FromKclValue<'a> for crate::execution::Metadata {
868    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
869        FromKclValue::from_kcl_val(arg).map(|sr| Self { source_range: sr })
870    }
871}
872
873impl<'a> FromKclValue<'a> for crate::execution::Solid {
874    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
875        arg.as_solid().cloned()
876    }
877}
878
879impl<'a> FromKclValue<'a> for crate::execution::SolidOrSketchOrImportedGeometry {
880    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
881        match arg {
882            KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])),
883            KclValue::Sketch { value } => Some(Self::SketchSet(vec![(**value).clone()])),
884            KclValue::HomArray { value, .. } => {
885                let mut solids = vec![];
886                let mut sketches = vec![];
887                for item in value {
888                    match item {
889                        KclValue::Solid { value } => solids.push((**value).clone()),
890                        KclValue::Sketch { value } => sketches.push((**value).clone()),
891                        _ => return None,
892                    }
893                }
894                if !solids.is_empty() {
895                    Some(Self::SolidSet(solids))
896                } else {
897                    Some(Self::SketchSet(sketches))
898                }
899            }
900            KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
901            _ => None,
902        }
903    }
904}
905
906impl<'a> FromKclValue<'a> for crate::execution::SolidOrImportedGeometry {
907    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
908        match arg {
909            KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])),
910            KclValue::HomArray { value, .. } => {
911                let mut solids = vec![];
912                for item in value {
913                    match item {
914                        KclValue::Solid { value } => solids.push((**value).clone()),
915                        _ => return None,
916                    }
917                }
918                Some(Self::SolidSet(solids))
919            }
920            KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
921            _ => None,
922        }
923    }
924}
925
926impl<'a> FromKclValue<'a> for super::sketch::SketchData {
927    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
928        // Order is critical since PlaneData is a subset of Plane.
929        let case1 = crate::execution::Plane::from_kcl_val;
930        let case2 = super::sketch::PlaneData::from_kcl_val;
931        let case3 = crate::execution::Solid::from_kcl_val;
932        let case4 = <Vec<Solid>>::from_kcl_val;
933        case1(arg)
934            .map(Box::new)
935            .map(Self::Plane)
936            .or_else(|| case2(arg).map(Self::PlaneOrientation))
937            .or_else(|| case3(arg).map(Box::new).map(Self::Solid))
938            .or_else(|| case4(arg).map(|v| Box::new(v[0].clone())).map(Self::Solid))
939    }
940}
941
942impl<'a> FromKclValue<'a> for super::fillet::EdgeReference {
943    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
944        let id = arg.as_uuid().map(Self::Uuid);
945        let tag = || TagIdentifier::from_kcl_val(arg).map(Box::new).map(Self::Tag);
946        id.or_else(tag)
947    }
948}
949
950impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis2dOrEdgeReference {
951    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
952        let case1 = |arg: &KclValue| {
953            let obj = arg.as_object()?;
954            let_field_of!(obj, direction);
955            let_field_of!(obj, origin);
956            Some(Self::Axis { direction, origin })
957        };
958        let case2 = super::fillet::EdgeReference::from_kcl_val;
959        case1(arg).or_else(|| case2(arg).map(Self::Edge))
960    }
961}
962
963impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrEdgeReference {
964    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
965        let case1 = |arg: &KclValue| {
966            let obj = arg.as_object()?;
967            let_field_of!(obj, direction);
968            let_field_of!(obj, origin);
969            Some(Self::Axis { direction, origin })
970        };
971        let case2 = super::fillet::EdgeReference::from_kcl_val;
972        case1(arg).or_else(|| case2(arg).map(Self::Edge))
973    }
974}
975
976impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis2dOrPoint2d {
977    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
978        let case1 = |arg: &KclValue| {
979            let obj = arg.as_object()?;
980            let_field_of!(obj, direction);
981            let_field_of!(obj, origin);
982            Some(Self::Axis { direction, origin })
983        };
984        let case2 = <[TyF64; 2]>::from_kcl_val;
985        case1(arg).or_else(|| case2(arg).map(Self::Point))
986    }
987}
988
989impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrPoint3d {
990    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
991        let case1 = |arg: &KclValue| {
992            let obj = arg.as_object()?;
993            let_field_of!(obj, direction);
994            let_field_of!(obj, origin);
995            Some(Self::Axis { direction, origin })
996        };
997        let case2 = <[TyF64; 3]>::from_kcl_val;
998        case1(arg).or_else(|| case2(arg).map(Self::Point))
999    }
1000}
1001
1002impl<'a> FromKclValue<'a> for super::axis_or_reference::Point3dAxis3dOrGeometryReference {
1003    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1004        let case1 = |arg: &KclValue| {
1005            let obj = arg.as_object()?;
1006            let_field_of!(obj, direction);
1007            let_field_of!(obj, origin);
1008            Some(Self::Axis { direction, origin })
1009        };
1010        let case2 = <[TyF64; 3]>::from_kcl_val;
1011        let case3 = super::fillet::EdgeReference::from_kcl_val;
1012        let case4 = FaceTag::from_kcl_val;
1013        let case5 = Box::<Solid>::from_kcl_val;
1014        let case6 = TagIdentifier::from_kcl_val;
1015        let case7 = Box::<Plane>::from_kcl_val;
1016        let case8 = Box::<Sketch>::from_kcl_val;
1017
1018        case1(arg)
1019            .or_else(|| case2(arg).map(Self::Point))
1020            .or_else(|| case3(arg).map(Self::Edge))
1021            .or_else(|| case4(arg).map(Self::Face))
1022            .or_else(|| case5(arg).map(Self::Solid))
1023            .or_else(|| case6(arg).map(Self::TaggedEdgeOrFace))
1024            .or_else(|| case7(arg).map(Self::Plane))
1025            .or_else(|| case8(arg).map(Self::Sketch))
1026    }
1027}
1028
1029impl<'a> FromKclValue<'a> for i64 {
1030    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1031        match arg {
1032            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
1033            _ => None,
1034        }
1035    }
1036}
1037
1038impl<'a> FromKclValue<'a> for &'a str {
1039    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1040        let KclValue::String { value, meta: _ } = arg else {
1041            return None;
1042        };
1043        Some(value)
1044    }
1045}
1046
1047impl<'a> FromKclValue<'a> for &'a KclObjectFields {
1048    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1049        let KclValue::Object { value, .. } = arg else {
1050            return None;
1051        };
1052        Some(value)
1053    }
1054}
1055
1056impl<'a> FromKclValue<'a> for uuid::Uuid {
1057    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1058        let KclValue::Uuid { value, meta: _ } = arg else {
1059            return None;
1060        };
1061        Some(*value)
1062    }
1063}
1064
1065impl<'a> FromKclValue<'a> for u32 {
1066    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1067        match arg {
1068            KclValue::Number { value, .. } => crate::try_f64_to_u32(*value),
1069            _ => None,
1070        }
1071    }
1072}
1073
1074impl<'a> FromKclValue<'a> for NonZeroU32 {
1075    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1076        u32::from_kcl_val(arg).and_then(|x| x.try_into().ok())
1077    }
1078}
1079
1080impl<'a> FromKclValue<'a> for u64 {
1081    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1082        match arg {
1083            KclValue::Number { value, .. } => crate::try_f64_to_u64(*value),
1084            _ => None,
1085        }
1086    }
1087}
1088
1089impl<'a> FromKclValue<'a> for TyF64 {
1090    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1091        match arg {
1092            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
1093            _ => None,
1094        }
1095    }
1096}
1097
1098impl<'a> FromKclValue<'a> for [TyF64; 2] {
1099    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1100        match arg {
1101            KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
1102                if value.len() != 2 {
1103                    return None;
1104                }
1105                let v0 = value.first()?;
1106                let v1 = value.get(1)?;
1107                let array = [v0.as_ty_f64()?, v1.as_ty_f64()?];
1108                Some(array)
1109            }
1110            _ => None,
1111        }
1112    }
1113}
1114
1115impl<'a> FromKclValue<'a> for [TyF64; 3] {
1116    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1117        match arg {
1118            KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
1119                if value.len() != 3 {
1120                    return None;
1121                }
1122                let v0 = value.first()?;
1123                let v1 = value.get(1)?;
1124                let v2 = value.get(2)?;
1125                let array = [v0.as_ty_f64()?, v1.as_ty_f64()?, v2.as_ty_f64()?];
1126                Some(array)
1127            }
1128            _ => None,
1129        }
1130    }
1131}
1132
1133impl<'a> FromKclValue<'a> for [TyF64; 6] {
1134    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1135        match arg {
1136            KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
1137                if value.len() != 6 {
1138                    return None;
1139                }
1140                let v0 = value.first()?;
1141                let v1 = value.get(1)?;
1142                let v2 = value.get(2)?;
1143                let v3 = value.get(3)?;
1144                let v4 = value.get(4)?;
1145                let v5 = value.get(5)?;
1146                let array = [
1147                    v0.as_ty_f64()?,
1148                    v1.as_ty_f64()?,
1149                    v2.as_ty_f64()?,
1150                    v3.as_ty_f64()?,
1151                    v4.as_ty_f64()?,
1152                    v5.as_ty_f64()?,
1153                ];
1154                Some(array)
1155            }
1156            _ => None,
1157        }
1158    }
1159}
1160
1161impl<'a> FromKclValue<'a> for Sketch {
1162    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1163        let KclValue::Sketch { value } = arg else {
1164            return None;
1165        };
1166        Some(value.as_ref().to_owned())
1167    }
1168}
1169
1170impl<'a> FromKclValue<'a> for Helix {
1171    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1172        let KclValue::Helix { value } = arg else {
1173            return None;
1174        };
1175        Some(value.as_ref().to_owned())
1176    }
1177}
1178
1179impl<'a> FromKclValue<'a> for SweepPath {
1180    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1181        let case1 = Sketch::from_kcl_val;
1182        let case2 = <Vec<Sketch>>::from_kcl_val;
1183        let case3 = Helix::from_kcl_val;
1184        case1(arg)
1185            .map(Self::Sketch)
1186            .or_else(|| case2(arg).map(|arg0: Vec<Sketch>| Self::Sketch(arg0[0].clone())))
1187            .or_else(|| case3(arg).map(|arg0: Helix| Self::Helix(Box::new(arg0))))
1188    }
1189}
1190impl<'a> FromKclValue<'a> for String {
1191    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1192        let KclValue::String { value, meta: _ } = arg else {
1193            return None;
1194        };
1195        Some(value.to_owned())
1196    }
1197}
1198impl<'a> FromKclValue<'a> for crate::parsing::ast::types::KclNone {
1199    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1200        let KclValue::KclNone { value, meta: _ } = arg else {
1201            return None;
1202        };
1203        Some(value.to_owned())
1204    }
1205}
1206impl<'a> FromKclValue<'a> for bool {
1207    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1208        let KclValue::Bool { value, meta: _ } = arg else {
1209            return None;
1210        };
1211        Some(*value)
1212    }
1213}
1214
1215impl<'a> FromKclValue<'a> for Box<Solid> {
1216    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1217        let KclValue::Solid { value } = arg else {
1218            return None;
1219        };
1220        Some(value.to_owned())
1221    }
1222}
1223
1224impl<'a> FromKclValue<'a> for Box<Plane> {
1225    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1226        let KclValue::Plane { value } = arg else {
1227            return None;
1228        };
1229        Some(value.to_owned())
1230    }
1231}
1232
1233impl<'a> FromKclValue<'a> for Box<Sketch> {
1234    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1235        let KclValue::Sketch { value } = arg else {
1236            return None;
1237        };
1238        Some(value.to_owned())
1239    }
1240}
1241
1242impl<'a> FromKclValue<'a> for FunctionSource {
1243    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1244        arg.as_function().cloned()
1245    }
1246}
1247
1248impl<'a> FromKclValue<'a> for SketchOrSurface {
1249    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1250        match arg {
1251            KclValue::Sketch { value: sg } => Some(Self::Sketch(sg.to_owned())),
1252            KclValue::Plane { value } => Some(Self::SketchSurface(SketchSurface::Plane(value.clone()))),
1253            KclValue::Face { value } => Some(Self::SketchSurface(SketchSurface::Face(value.clone()))),
1254            _ => None,
1255        }
1256    }
1257}
1258impl<'a> FromKclValue<'a> for SketchSurface {
1259    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1260        match arg {
1261            KclValue::Plane { value } => Some(Self::Plane(value.clone())),
1262            KclValue::Face { value } => Some(Self::Face(value.clone())),
1263            _ => None,
1264        }
1265    }
1266}
1267
1268impl From<Args> for Metadata {
1269    fn from(value: Args) -> Self {
1270        Self {
1271            source_range: value.source_range,
1272        }
1273    }
1274}
1275
1276impl From<Args> for Vec<Metadata> {
1277    fn from(value: Args) -> Self {
1278        vec![Metadata {
1279            source_range: value.source_range,
1280        }]
1281    }
1282}