kcl_lib/std/
args.rs

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