kcl_lib/std/
args.rs

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