kcl_lib/std/
args.rs

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