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