kcl_lib/std/
args.rs

1use std::{any::type_name, collections::HashMap, num::NonZeroU32};
2
3use anyhow::Result;
4use kcmc::{
5    websocket::{ModelingCmdReq, OkWebSocketResponseData},
6    ModelingCmd,
7};
8use kittycad_modeling_cmds as kcmc;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12use crate::{
13    errors::{KclError, KclErrorDetails},
14    execution::{
15        kcl_value::{ArrayLen, FunctionSource, NumericType, RuntimeType},
16        ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PrimitiveType, Sketch,
17        SketchSurface, Solid, TagIdentifier,
18    },
19    parsing::ast::types::TagNode,
20    source_range::SourceRange,
21    std::{
22        shapes::{PolygonType, SketchOrSurface},
23        sketch::FaceTag,
24        sweep::SweepPath,
25    },
26    ModuleId,
27};
28
29#[derive(Debug, Clone)]
30pub struct Arg {
31    /// The evaluated argument.
32    pub value: KclValue,
33    /// The source range of the unevaluated argument.
34    pub source_range: SourceRange,
35}
36
37impl Arg {
38    pub fn new(value: KclValue, source_range: SourceRange) -> Self {
39        Self { value, source_range }
40    }
41
42    pub fn synthetic(value: KclValue) -> Self {
43        Self {
44            value,
45            source_range: SourceRange::synthetic(),
46        }
47    }
48
49    pub fn source_ranges(&self) -> Vec<SourceRange> {
50        vec![self.source_range]
51    }
52}
53
54#[derive(Debug, Clone, Default)]
55pub struct KwArgs {
56    /// Unlabeled keyword args. Currently only the first arg can be unlabeled.
57    pub unlabeled: Option<Arg>,
58    /// Labeled args.
59    pub labeled: HashMap<String, Arg>,
60}
61
62impl KwArgs {
63    /// How many arguments are there?
64    pub fn len(&self) -> usize {
65        self.labeled.len() + if self.unlabeled.is_some() { 1 } else { 0 }
66    }
67    /// Are there no arguments?
68    pub fn is_empty(&self) -> bool {
69        self.labeled.len() == 0 && self.unlabeled.is_none()
70    }
71}
72
73#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
74#[ts(export)]
75#[serde(rename_all = "camelCase")]
76pub struct TyF64 {
77    pub n: f64,
78    pub ty: NumericType,
79}
80
81impl TyF64 {
82    pub fn new(n: f64, ty: NumericType) -> Self {
83        Self { n, ty }
84    }
85
86    pub fn count(n: f64) -> Self {
87        Self {
88            n,
89            ty: NumericType::count(),
90        }
91    }
92
93    pub fn map(mut self, n: f64) -> Self {
94        self.n = n;
95        self
96    }
97}
98
99#[derive(Debug, Clone)]
100pub struct Args {
101    /// Positional args.
102    pub args: Vec<Arg>,
103    /// Keyword arguments
104    pub kw_args: KwArgs,
105    pub source_range: SourceRange,
106    pub ctx: ExecutorContext,
107    /// If this call happens inside a pipe (|>) expression, this holds the LHS of that |>.
108    /// Otherwise it's None.
109    pipe_value: Option<Arg>,
110}
111
112impl Args {
113    pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
114        Self {
115            args,
116            kw_args: Default::default(),
117            source_range,
118            ctx,
119            pipe_value,
120        }
121    }
122
123    /// Collect the given keyword arguments.
124    pub fn new_kw(kw_args: KwArgs, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
125        Self {
126            args: Default::default(),
127            kw_args,
128            source_range,
129            ctx,
130            pipe_value,
131        }
132    }
133
134    /// Get a keyword argument. If not set, returns None.
135    pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Result<Option<T>, KclError>
136    where
137        T: FromKclValue<'a>,
138    {
139        let Some(arg) = self.kw_args.labeled.get(label) else {
140            return Ok(None);
141        };
142
143        T::from_kcl_val(&arg.value).map(Some).ok_or_else(|| {
144            KclError::Type(KclErrorDetails {
145                source_ranges: vec![self.source_range],
146                message: format!(
147                    "The arg {label} was given, but it was the wrong type. It should be type {} but it was {}",
148                    type_name::<T>(),
149                    arg.value.human_friendly_type(),
150                ),
151            })
152        })
153    }
154
155    /// Get a keyword argument. If not set, returns Err.
156    pub(crate) fn get_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
157    where
158        T: FromKclValue<'a>,
159    {
160        self.get_kw_arg_opt(label)?.ok_or_else(|| {
161            KclError::Semantic(KclErrorDetails {
162                source_ranges: vec![self.source_range],
163                message: format!("This function requires a keyword argument '{label}'"),
164            })
165        })
166    }
167
168    pub(crate) fn get_kw_arg_typed<T>(
169        &self,
170        label: &str,
171        ty: &RuntimeType,
172        exec_state: &mut ExecState,
173    ) -> Result<T, KclError>
174    where
175        T: for<'a> FromKclValue<'a>,
176    {
177        let Some(arg) = self.kw_args.labeled.get(label) else {
178            return Err(KclError::Semantic(KclErrorDetails {
179                source_ranges: vec![self.source_range],
180                message: format!("This function requires a keyword argument '{label}'"),
181            }));
182        };
183
184        let arg = arg.value.coerce(ty, exec_state).ok_or_else(|| {
185            let actual_type_name = arg.value.human_friendly_type();
186            let msg_base = format!(
187                "This function expected the input argument to be {} but it's actually of type {actual_type_name}",
188                ty.human_friendly_type(),
189            );
190            let suggestion = match (ty, actual_type_name) {
191                (RuntimeType::Primitive(PrimitiveType::Solid), "Sketch")
192                | (RuntimeType::Array(PrimitiveType::Solid, _), "Sketch") => Some(
193                    "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
194                ),
195                _ => None,
196            };
197            let message = match suggestion {
198                None => msg_base,
199                Some(sugg) => format!("{msg_base}. {sugg}"),
200            };
201            KclError::Semantic(KclErrorDetails {
202                source_ranges: arg.source_ranges(),
203                message,
204            })
205        })?;
206
207        // TODO unnecessary cloning
208        Ok(T::from_kcl_val(&arg).unwrap())
209    }
210
211    /// Get a labelled keyword arg, check it's an array, and return all items in the array
212    /// plus their source range.
213    pub(crate) fn kw_arg_array_and_source<'a, T>(&'a self, label: &str) -> Result<Vec<(T, SourceRange)>, KclError>
214    where
215        T: FromKclValue<'a>,
216    {
217        let Some(arg) = self.kw_args.labeled.get(label) else {
218            let err = KclError::Semantic(KclErrorDetails {
219                source_ranges: vec![self.source_range],
220                message: format!("This function requires a keyword argument '{label}'"),
221            });
222            return Err(err);
223        };
224        let Some(array) = arg.value.as_array() else {
225            let err = KclError::Semantic(KclErrorDetails {
226                source_ranges: vec![arg.source_range],
227                message: format!(
228                    "Expected an array of {} but found {}",
229                    type_name::<T>(),
230                    arg.value.human_friendly_type()
231                ),
232            });
233            return Err(err);
234        };
235        array
236            .iter()
237            .map(|item| {
238                let source = SourceRange::from(item);
239                let val = FromKclValue::from_kcl_val(item).ok_or_else(|| {
240                    KclError::Semantic(KclErrorDetails {
241                        source_ranges: arg.source_ranges(),
242                        message: format!(
243                            "Expected a {} but found {}",
244                            type_name::<T>(),
245                            arg.value.human_friendly_type()
246                        ),
247                    })
248                })?;
249                Ok((val, source))
250            })
251            .collect::<Result<Vec<_>, _>>()
252    }
253
254    /// Get the unlabeled keyword argument. If not set, returns None.
255    pub(crate) fn unlabeled_kw_arg_unconverted(&self) -> Option<&Arg> {
256        self.kw_args
257            .unlabeled
258            .as_ref()
259            .or(self.args.first())
260            .or(self.pipe_value.as_ref())
261    }
262
263    /// Get the unlabeled keyword argument. If not set, returns Err.  If it
264    /// can't be converted to the given type, returns Err.
265    pub(crate) fn get_unlabeled_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
266    where
267        T: FromKclValue<'a>,
268    {
269        let arg = self
270            .unlabeled_kw_arg_unconverted()
271            .ok_or(KclError::Semantic(KclErrorDetails {
272                source_ranges: vec![self.source_range],
273                message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
274            }))?;
275
276        T::from_kcl_val(&arg.value).ok_or_else(|| {
277            let expected_type_name = tynm::type_name::<T>();
278            let actual_type_name = arg.value.human_friendly_type();
279            let message = format!("This function expected the input argument to be of type {expected_type_name} but it's actually of type {actual_type_name}");
280            KclError::Semantic(KclErrorDetails {
281                source_ranges: arg.source_ranges(),
282                message,
283            })
284        })
285    }
286
287    /// Get the unlabeled keyword argument. If not set, returns Err. If it
288    /// can't be converted to the given type, returns Err.
289    pub(crate) fn get_unlabeled_kw_arg_typed<T>(
290        &self,
291        label: &str,
292        ty: &RuntimeType,
293        exec_state: &mut ExecState,
294    ) -> Result<T, KclError>
295    where
296        T: for<'a> FromKclValue<'a>,
297    {
298        let arg = self
299            .unlabeled_kw_arg_unconverted()
300            .ok_or(KclError::Semantic(KclErrorDetails {
301                source_ranges: vec![self.source_range],
302                message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
303            }))?;
304
305        let arg = arg.value.coerce(ty, exec_state).ok_or_else(|| {
306            let actual_type_name = arg.value.human_friendly_type();
307            let msg_base = format!(
308                "This function expected the input argument to be {} but it's actually of type {actual_type_name}",
309                ty.human_friendly_type(),
310            );
311            let suggestion = match (ty, actual_type_name) {
312                (RuntimeType::Primitive(PrimitiveType::Solid), "Sketch")
313                | (RuntimeType::Array(PrimitiveType::Solid, _), "Sketch") => Some(
314                    "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
315                ),
316                _ => None,
317            };
318            let message = match suggestion {
319                None => msg_base,
320                Some(sugg) => format!("{msg_base}. {sugg}"),
321            };
322            KclError::Semantic(KclErrorDetails {
323                source_ranges: arg.source_ranges(),
324                message,
325            })
326        })?;
327
328        // TODO unnecessary cloning
329        Ok(T::from_kcl_val(&arg).unwrap())
330    }
331
332    // Add a modeling command to the batch but don't fire it right away.
333    pub(crate) async fn batch_modeling_cmd(
334        &self,
335        id: uuid::Uuid,
336        cmd: ModelingCmd,
337    ) -> Result<(), crate::errors::KclError> {
338        self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await
339    }
340
341    // Add multiple modeling commands to the batch but don't fire them right away.
342    pub(crate) async fn batch_modeling_cmds(&self, cmds: &[ModelingCmdReq]) -> Result<(), crate::errors::KclError> {
343        self.ctx.engine.batch_modeling_cmds(self.source_range, cmds).await
344    }
345
346    // Add a modeling commandSolid> to the batch that gets executed at the end of the file.
347    // This is good for something like fillet or chamfer where the engine would
348    // eat the path id if we executed it right away.
349    pub(crate) async fn batch_end_cmd(&self, id: uuid::Uuid, cmd: ModelingCmd) -> Result<(), crate::errors::KclError> {
350        self.ctx.engine.batch_end_cmd(id, self.source_range, &cmd).await
351    }
352
353    /// Send the modeling cmd and wait for the response.
354    pub(crate) async fn send_modeling_cmd(
355        &self,
356        id: uuid::Uuid,
357        cmd: ModelingCmd,
358    ) -> Result<OkWebSocketResponseData, KclError> {
359        self.ctx.engine.send_modeling_cmd(id, self.source_range, &cmd).await
360    }
361
362    fn get_tag_info_from_memory<'a, 'e>(
363        &'a self,
364        exec_state: &'e mut ExecState,
365        tag: &'a TagIdentifier,
366    ) -> Result<&'e crate::execution::TagEngineInfo, KclError> {
367        if let (epoch, KclValue::TagIdentifier(t)) =
368            exec_state.stack().get_from_call_stack(&tag.value, self.source_range)?
369        {
370            let info = t.get_info(epoch).ok_or_else(|| {
371                KclError::Type(KclErrorDetails {
372                    message: format!("Tag `{}` does not have engine info", tag.value),
373                    source_ranges: vec![self.source_range],
374                })
375            })?;
376            Ok(info)
377        } else {
378            Err(KclError::Type(KclErrorDetails {
379                message: format!("Tag `{}` does not exist", tag.value),
380                source_ranges: vec![self.source_range],
381            }))
382        }
383    }
384
385    pub(crate) fn get_tag_engine_info<'a, 'e>(
386        &'a self,
387        exec_state: &'e mut ExecState,
388        tag: &'a TagIdentifier,
389    ) -> Result<&'a crate::execution::TagEngineInfo, KclError>
390    where
391        'e: 'a,
392    {
393        if let Some(info) = tag.get_cur_info() {
394            return Ok(info);
395        }
396
397        self.get_tag_info_from_memory(exec_state, tag)
398    }
399
400    fn get_tag_engine_info_check_surface<'a, 'e>(
401        &'a self,
402        exec_state: &'e mut ExecState,
403        tag: &'a TagIdentifier,
404    ) -> Result<&'a crate::execution::TagEngineInfo, KclError>
405    where
406        'e: 'a,
407    {
408        if let Some(info) = tag.get_cur_info() {
409            if info.surface.is_some() {
410                return Ok(info);
411            }
412        }
413
414        self.get_tag_info_from_memory(exec_state, tag)
415    }
416
417    /// Flush just the fillets and chamfers for this specific SolidSet.
418    #[allow(clippy::vec_box)]
419    pub(crate) async fn flush_batch_for_solids(
420        &self,
421        exec_state: &mut ExecState,
422        solids: &[Solid],
423    ) -> Result<(), KclError> {
424        // Make sure we don't traverse sketches more than once.
425        let mut traversed_sketches = Vec::new();
426
427        // Collect all the fillet/chamfer ids for the solids.
428        let mut ids = Vec::new();
429        for solid in solids {
430            // We need to traverse the solids that share the same sketch.
431            let sketch_id = solid.sketch.id;
432            if !traversed_sketches.contains(&sketch_id) {
433                // Find all the solids on the same shared sketch.
434                ids.extend(
435                    exec_state
436                        .stack()
437                        .walk_call_stack()
438                        .filter(|v| matches!(v, KclValue::Solid { value } if value.sketch.id == sketch_id))
439                        .flat_map(|v| match v {
440                            KclValue::Solid { value } => value.get_all_edge_cut_ids(),
441                            _ => unreachable!(),
442                        }),
443                );
444                traversed_sketches.push(sketch_id);
445            }
446
447            ids.extend(solid.get_all_edge_cut_ids());
448        }
449
450        // We can return early if there are no fillets or chamfers.
451        if ids.is_empty() {
452            return Ok(());
453        }
454
455        // We want to move these fillets and chamfers from batch_end to batch so they get executed
456        // before what ever we call next.
457        for id in ids {
458            // Pop it off the batch_end and add it to the batch.
459            let Some(item) = self.ctx.engine.batch_end().write().await.shift_remove(&id) else {
460                // It might be in the batch already.
461                continue;
462            };
463            // Add it to the batch.
464            self.ctx.engine.batch().write().await.push(item);
465        }
466
467        // Run flush.
468        // Yes, we do need to actually flush the batch here, or references will fail later.
469        self.ctx.engine.flush_batch(false, self.source_range).await?;
470
471        Ok(())
472    }
473
474    pub(crate) fn make_user_val_from_point(&self, p: [f64; 2]) -> Result<KclValue, KclError> {
475        let meta = Metadata {
476            source_range: self.source_range,
477        };
478        let x = KclValue::Number {
479            value: p[0],
480            meta: vec![meta],
481            ty: NumericType::Unknown,
482        };
483        let y = KclValue::Number {
484            value: p[1],
485            meta: vec![meta],
486            ty: NumericType::Unknown,
487        };
488        Ok(KclValue::MixedArray {
489            value: vec![x, y],
490            meta: vec![meta],
491        })
492    }
493
494    pub(crate) fn make_user_val_from_f64(&self, f: f64) -> KclValue {
495        KclValue::from_number(
496            f,
497            vec![Metadata {
498                source_range: self.source_range,
499            }],
500        )
501    }
502
503    pub(crate) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
504        KclValue::from_number_with_type(
505            f.n,
506            f.ty,
507            vec![Metadata {
508                source_range: self.source_range,
509            }],
510        )
511    }
512
513    pub(crate) fn make_user_val_from_f64_array(&self, f: Vec<f64>, ty: &NumericType) -> Result<KclValue, KclError> {
514        let array = f
515            .into_iter()
516            .map(|n| KclValue::Number {
517                value: n,
518                meta: vec![Metadata {
519                    source_range: self.source_range,
520                }],
521                ty: ty.clone(),
522            })
523            .collect::<Vec<_>>();
524        Ok(KclValue::MixedArray {
525            value: array,
526            meta: vec![Metadata {
527                source_range: self.source_range,
528            }],
529        })
530    }
531
532    pub(crate) fn get_number(&self) -> Result<f64, KclError> {
533        FromArgs::from_args(self, 0)
534    }
535
536    pub(crate) fn get_number_with_type(&self) -> Result<TyF64, KclError> {
537        FromArgs::from_args(self, 0)
538    }
539
540    pub(crate) fn get_number_array(&self) -> Result<Vec<f64>, KclError> {
541        let numbers = self
542            .args
543            .iter()
544            .map(|arg| {
545                let Some(num) = f64::from_kcl_val(&arg.value) else {
546                    return Err(KclError::Semantic(KclErrorDetails {
547                        source_ranges: arg.source_ranges(),
548                        message: format!("Expected a number but found {}", arg.value.human_friendly_type()),
549                    }));
550                };
551                Ok(num)
552            })
553            .collect::<Result<_, _>>()?;
554        Ok(numbers)
555    }
556
557    pub(crate) fn get_number_array_with_types(&self) -> Result<Vec<TyF64>, KclError> {
558        let numbers = self
559            .args
560            .iter()
561            .map(|arg| {
562                let Some(num) = <TyF64>::from_kcl_val(&arg.value) else {
563                    return Err(KclError::Semantic(KclErrorDetails {
564                        source_ranges: arg.source_ranges(),
565                        message: format!("Expected a number but found {}", arg.value.human_friendly_type()),
566                    }));
567                };
568                Ok(num)
569            })
570            .collect::<Result<_, _>>()?;
571        Ok(numbers)
572    }
573
574    pub(crate) fn get_hypotenuse_leg(&self) -> Result<(f64, f64, NumericType), KclError> {
575        let numbers = self.get_number_array_with_types()?;
576
577        if numbers.len() != 2 {
578            return Err(KclError::Type(KclErrorDetails {
579                message: format!("Expected a number array of length 2, found `{:?}`", numbers),
580                source_ranges: vec![self.source_range],
581            }));
582        }
583
584        let mut numbers = numbers.into_iter();
585        let a = numbers.next().unwrap();
586        let b = numbers.next().unwrap();
587        let ty = a.ty.combine_eq(&b.ty);
588        Ok((a.n, b.n, ty))
589    }
590
591    pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> {
592        let Some(arg0) = self.args.first() else {
593            return Err(KclError::Semantic(KclErrorDetails {
594                message: "Expected a sketch argument".to_owned(),
595                source_ranges: vec![self.source_range],
596            }));
597        };
598        let sarg = arg0
599            .value
600            .coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state)
601            .ok_or(KclError::Type(KclErrorDetails {
602                message: format!(
603                    "Expected an array of sketches, found {}",
604                    arg0.value.human_friendly_type()
605                ),
606                source_ranges: vec![self.source_range],
607            }))?;
608        let sketches = match sarg {
609            KclValue::HomArray { value, .. } => value.iter().map(|v| v.as_sketch().unwrap().clone()).collect(),
610            _ => unreachable!(),
611        };
612
613        let Some(arg1) = self.args.get(1) else {
614            return Err(KclError::Semantic(KclErrorDetails {
615                message: "Expected a second sketch argument".to_owned(),
616                source_ranges: vec![self.source_range],
617            }));
618        };
619        let sarg = arg1
620            .value
621            .coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
622            .ok_or(KclError::Type(KclErrorDetails {
623                message: format!("Expected a sketch, found {}", arg1.value.human_friendly_type()),
624                source_ranges: vec![self.source_range],
625            }))?;
626        let sketch = match sarg {
627            KclValue::Sketch { value } => *value,
628            _ => unreachable!(),
629        };
630
631        Ok((sketches, sketch))
632    }
633
634    pub(crate) fn get_sketch(&self, exec_state: &mut ExecState) -> Result<Sketch, KclError> {
635        let Some(arg0) = self.args.first() else {
636            return Err(KclError::Semantic(KclErrorDetails {
637                message: "Expected a sketch argument".to_owned(),
638                source_ranges: vec![self.source_range],
639            }));
640        };
641        let sarg = arg0
642            .value
643            .coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
644            .ok_or(KclError::Type(KclErrorDetails {
645                message: format!("Expected a sketch, found {}", arg0.value.human_friendly_type()),
646                source_ranges: vec![self.source_range],
647            }))?;
648        match sarg {
649            KclValue::Sketch { value } => Ok(*value),
650            _ => unreachable!(),
651        }
652    }
653
654    pub(crate) fn get_data<'a, T>(&'a self) -> Result<T, KclError>
655    where
656        T: FromArgs<'a> + serde::de::DeserializeOwned,
657    {
658        FromArgs::from_args(self, 0)
659    }
660
661    pub(crate) fn get_import_data(&self) -> Result<(String, Option<crate::std::import::ImportFormat>), KclError> {
662        FromArgs::from_args(self, 0)
663    }
664
665    pub(crate) fn get_data_and_optional_tag<'a, T>(&'a self) -> Result<(T, Option<FaceTag>), KclError>
666    where
667        T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
668    {
669        FromArgs::from_args(self, 0)
670    }
671
672    pub(crate) fn get_data_and_sketches<'a, T>(
673        &'a self,
674        exec_state: &mut ExecState,
675    ) -> Result<(T, Vec<Sketch>), KclError>
676    where
677        T: serde::de::DeserializeOwned + FromArgs<'a>,
678    {
679        let data: T = FromArgs::from_args(self, 0)?;
680        let Some(arg1) = self.args.get(1) else {
681            return Err(KclError::Semantic(KclErrorDetails {
682                message: "Expected one or more sketches for second argument".to_owned(),
683                source_ranges: vec![self.source_range],
684            }));
685        };
686        let sarg = arg1
687            .value
688            .coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state)
689            .ok_or(KclError::Type(KclErrorDetails {
690                message: format!(
691                    "Expected one or more sketches for second argument, found {}",
692                    arg1.value.human_friendly_type()
693                ),
694                source_ranges: vec![self.source_range],
695            }))?;
696        let sketches = match sarg {
697            KclValue::HomArray { value, .. } => value.iter().map(|v| v.as_sketch().unwrap().clone()).collect(),
698            _ => unreachable!(),
699        };
700        Ok((data, sketches))
701    }
702
703    pub(crate) fn get_data_and_sketch_and_tag<'a, T>(
704        &'a self,
705        exec_state: &mut ExecState,
706    ) -> Result<(T, Sketch, Option<TagNode>), KclError>
707    where
708        T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
709    {
710        let data: T = FromArgs::from_args(self, 0)?;
711        let Some(arg1) = self.args.get(1) else {
712            return Err(KclError::Semantic(KclErrorDetails {
713                message: "Expected a sketch for second argument".to_owned(),
714                source_ranges: vec![self.source_range],
715            }));
716        };
717        let sarg = arg1
718            .value
719            .coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
720            .ok_or(KclError::Type(KclErrorDetails {
721                message: format!(
722                    "Expected a sketch for second argument, found {}",
723                    arg1.value.human_friendly_type()
724                ),
725                source_ranges: vec![self.source_range],
726            }))?;
727        let sketch = match sarg {
728            KclValue::Sketch { value } => *value,
729            _ => unreachable!(),
730        };
731        let tag: Option<TagNode> = FromArgs::from_args(self, 2)?;
732        Ok((data, sketch, tag))
733    }
734
735    pub(crate) fn get_data_and_sketch_surface<'a, T>(&'a self) -> Result<(T, SketchSurface, Option<TagNode>), KclError>
736    where
737        T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
738    {
739        FromArgs::from_args(self, 0)
740    }
741
742    pub(crate) fn get_data_and_solid<'a, T>(&'a self, exec_state: &mut ExecState) -> Result<(T, Box<Solid>), KclError>
743    where
744        T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
745    {
746        let data: T = FromArgs::from_args(self, 0)?;
747        let Some(arg1) = self.args.get(1) else {
748            return Err(KclError::Semantic(KclErrorDetails {
749                message: "Expected a solid for second argument".to_owned(),
750                source_ranges: vec![self.source_range],
751            }));
752        };
753        let sarg = arg1
754            .value
755            .coerce(&RuntimeType::Primitive(PrimitiveType::Solid), exec_state)
756            .ok_or(KclError::Type(KclErrorDetails {
757                message: format!(
758                    "Expected a solid for second argument, found {}",
759                    arg1.value.human_friendly_type()
760                ),
761                source_ranges: vec![self.source_range],
762            }))?;
763        let solid = match sarg {
764            KclValue::Solid { value } => value,
765            _ => unreachable!(),
766        };
767        Ok((data, solid))
768    }
769
770    pub(crate) fn get_tag_to_number_sketch(&self) -> Result<(TagIdentifier, f64, Sketch), KclError> {
771        FromArgs::from_args(self, 0)
772    }
773
774    pub(crate) async fn get_adjacent_face_to_tag(
775        &self,
776        exec_state: &mut ExecState,
777        tag: &TagIdentifier,
778        must_be_planar: bool,
779    ) -> Result<uuid::Uuid, KclError> {
780        if tag.value.is_empty() {
781            return Err(KclError::Type(KclErrorDetails {
782                message: "Expected a non-empty tag for the face".to_string(),
783                source_ranges: vec![self.source_range],
784            }));
785        }
786
787        let engine_info = self.get_tag_engine_info_check_surface(exec_state, tag)?;
788
789        let surface = engine_info.surface.as_ref().ok_or_else(|| {
790            KclError::Type(KclErrorDetails {
791                message: format!("Tag `{}` does not have a surface", tag.value),
792                source_ranges: vec![self.source_range],
793            })
794        })?;
795
796        if let Some(face_from_surface) = match surface {
797            ExtrudeSurface::ExtrudePlane(extrude_plane) => {
798                if let Some(plane_tag) = &extrude_plane.tag {
799                    if plane_tag.name == tag.value {
800                        Some(Ok(extrude_plane.face_id))
801                    } else {
802                        None
803                    }
804                } else {
805                    None
806                }
807            }
808            // The must be planar check must be called before the arc check.
809            ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails {
810                message: format!("Tag `{}` is a non-planar surface", tag.value),
811                source_ranges: vec![self.source_range],
812            }))),
813            ExtrudeSurface::ExtrudeArc(extrude_arc) => {
814                if let Some(arc_tag) = &extrude_arc.tag {
815                    if arc_tag.name == tag.value {
816                        Some(Ok(extrude_arc.face_id))
817                    } else {
818                        None
819                    }
820                } else {
821                    None
822                }
823            }
824            ExtrudeSurface::Chamfer(chamfer) => {
825                if let Some(chamfer_tag) = &chamfer.tag {
826                    if chamfer_tag.name == tag.value {
827                        Some(Ok(chamfer.face_id))
828                    } else {
829                        None
830                    }
831                } else {
832                    None
833                }
834            }
835            // The must be planar check must be called before the fillet check.
836            ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails {
837                message: format!("Tag `{}` is a non-planar surface", tag.value),
838                source_ranges: vec![self.source_range],
839            }))),
840            ExtrudeSurface::Fillet(fillet) => {
841                if let Some(fillet_tag) = &fillet.tag {
842                    if fillet_tag.name == tag.value {
843                        Some(Ok(fillet.face_id))
844                    } else {
845                        None
846                    }
847                } else {
848                    None
849                }
850            }
851        } {
852            return face_from_surface;
853        }
854
855        // If we still haven't found the face, return an error.
856        Err(KclError::Type(KclErrorDetails {
857            message: format!("Expected a face with the tag `{}`", tag.value),
858            source_ranges: vec![self.source_range],
859        }))
860    }
861
862    pub(crate) fn get_polygon_args(
863        &self,
864    ) -> Result<
865        (
866            crate::std::shapes::PolygonData,
867            crate::std::shapes::SketchOrSurface,
868            Option<TagNode>,
869        ),
870        KclError,
871    > {
872        FromArgs::from_args(self, 0)
873    }
874}
875
876/// Types which impl this trait can be read out of the `Args` passed into a KCL function.
877pub trait FromArgs<'a>: Sized {
878    /// Get this type from the args passed into a KCL function, at the given index in the argument list.
879    fn from_args(args: &'a Args, index: usize) -> Result<Self, KclError>;
880}
881
882/// Types which impl this trait can be extracted from a `KclValue`.
883pub trait FromKclValue<'a>: Sized {
884    /// Try to convert a KclValue into this type.
885    fn from_kcl_val(arg: &'a KclValue) -> Option<Self>;
886}
887
888impl<'a> FromArgs<'a> for Vec<KclValue> {
889    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
890        let Some(arg) = args.args.get(i) else {
891            return Err(KclError::Semantic(KclErrorDetails {
892                message: format!("Expected an argument at index {i}"),
893                source_ranges: vec![args.source_range],
894            }));
895        };
896        let KclValue::MixedArray { value: array, meta: _ } = &arg.value else {
897            let message = format!("Expected an array but found {}", arg.value.human_friendly_type());
898            return Err(KclError::Type(KclErrorDetails {
899                source_ranges: arg.source_ranges(),
900                message,
901            }));
902        };
903        Ok(array.to_owned())
904    }
905}
906
907impl<'a, T> FromArgs<'a> for T
908where
909    T: FromKclValue<'a> + Sized,
910{
911    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
912        let Some(arg) = args.args.get(i) else {
913            return Err(KclError::Semantic(KclErrorDetails {
914                message: format!("Expected an argument at index {i}"),
915                source_ranges: vec![args.source_range],
916            }));
917        };
918        let Some(val) = T::from_kcl_val(&arg.value) else {
919            return Err(KclError::Semantic(KclErrorDetails {
920                message: format!(
921                    "Argument at index {i} was supposed to be type {} but found {}",
922                    type_name::<T>(),
923                    arg.value.human_friendly_type(),
924                ),
925                source_ranges: arg.source_ranges(),
926            }));
927        };
928        Ok(val)
929    }
930}
931
932impl<'a, T> FromArgs<'a> for Option<T>
933where
934    T: FromKclValue<'a> + Sized,
935{
936    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
937        let Some(arg) = args.args.get(i) else { return Ok(None) };
938        if crate::parsing::ast::types::KclNone::from_kcl_val(&arg.value).is_some() {
939            return Ok(None);
940        }
941        let Some(val) = T::from_kcl_val(&arg.value) else {
942            return Err(KclError::Semantic(KclErrorDetails {
943                message: format!(
944                    "Argument at index {i} was supposed to be type Option<{}> but found {}",
945                    type_name::<T>(),
946                    arg.value.human_friendly_type()
947                ),
948                source_ranges: arg.source_ranges(),
949            }));
950        };
951        Ok(Some(val))
952    }
953}
954
955impl<'a, A, B> FromArgs<'a> for (A, B)
956where
957    A: FromArgs<'a>,
958    B: FromArgs<'a>,
959{
960    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
961        let a = A::from_args(args, i)?;
962        let b = B::from_args(args, i + 1)?;
963        Ok((a, b))
964    }
965}
966
967impl<'a, A, B, C> FromArgs<'a> for (A, B, C)
968where
969    A: FromArgs<'a>,
970    B: FromArgs<'a>,
971    C: FromArgs<'a>,
972{
973    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
974        let a = A::from_args(args, i)?;
975        let b = B::from_args(args, i + 1)?;
976        let c = C::from_args(args, i + 2)?;
977        Ok((a, b, c))
978    }
979}
980impl<'a, A, B, C, D> FromArgs<'a> for (A, B, C, D)
981where
982    A: FromArgs<'a>,
983    B: FromArgs<'a>,
984    C: FromArgs<'a>,
985    D: FromArgs<'a>,
986{
987    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
988        let a = A::from_args(args, i)?;
989        let b = B::from_args(args, i + 1)?;
990        let c = C::from_args(args, i + 2)?;
991        let d = D::from_args(args, i + 3)?;
992        Ok((a, b, c, d))
993    }
994}
995
996impl<'a> FromKclValue<'a> for [f64; 2] {
997    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
998        let KclValue::MixedArray { value, meta: _ } = arg else {
999            return None;
1000        };
1001        if value.len() != 2 {
1002            return None;
1003        }
1004        let v0 = value.first()?;
1005        let v1 = value.get(1)?;
1006        let array = [v0.as_f64()?, v1.as_f64()?];
1007        Some(array)
1008    }
1009}
1010
1011impl<'a> FromKclValue<'a> for [usize; 3] {
1012    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1013        let KclValue::MixedArray { value, meta: _ } = arg else {
1014            return None;
1015        };
1016        if value.len() != 3 {
1017            return None;
1018        }
1019        let v0 = value.first()?;
1020        let v1 = value.get(1)?;
1021        let v2 = value.get(2)?;
1022        let array = [v0.as_usize()?, v1.as_usize()?, v2.as_usize()?];
1023        Some(array)
1024    }
1025}
1026
1027impl<'a> FromKclValue<'a> for [f64; 3] {
1028    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1029        let KclValue::MixedArray { value, meta: _ } = arg else {
1030            return None;
1031        };
1032        if value.len() != 3 {
1033            return None;
1034        }
1035        let v0 = value.first()?;
1036        let v1 = value.get(1)?;
1037        let v2 = value.get(2)?;
1038        let array = [v0.as_f64()?, v1.as_f64()?, v2.as_f64()?];
1039        Some(array)
1040    }
1041}
1042
1043impl<'a> FromKclValue<'a> for TagNode {
1044    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1045        arg.get_tag_declarator().ok()
1046    }
1047}
1048
1049impl<'a> FromKclValue<'a> for TagIdentifier {
1050    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1051        arg.get_tag_identifier().ok()
1052    }
1053}
1054
1055impl<'a> FromKclValue<'a> for Vec<TagIdentifier> {
1056    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1057        match arg {
1058            KclValue::HomArray { value, .. } => {
1059                let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
1060                Some(tags)
1061            }
1062            KclValue::MixedArray { value, .. } => {
1063                let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
1064                Some(tags)
1065            }
1066            _ => None,
1067        }
1068    }
1069}
1070
1071impl<'a> FromKclValue<'a> for KclValue {
1072    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1073        Some(arg.clone())
1074    }
1075}
1076
1077macro_rules! let_field_of {
1078    // Optional field
1079    ($obj:ident, $field:ident?) => {
1080        let $field = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val);
1081    };
1082    // Optional field but with a different string used as the key
1083    ($obj:ident, $field:ident? $key:literal) => {
1084        let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val);
1085    };
1086    // Mandatory field, but with a different string used as the key.
1087    ($obj:ident, $field:ident $key:literal) => {
1088        let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val)?;
1089    };
1090    // Mandatory field, optionally with a type annotation
1091    ($obj:ident, $field:ident $(, $annotation:ty)?) => {
1092        let $field $(: $annotation)? = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val)?;
1093    };
1094}
1095
1096impl<'a> FromKclValue<'a> for crate::std::import::ImportFormat {
1097    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1098        let obj = arg.as_object()?;
1099        let_field_of!(obj, typ "format");
1100        match typ {
1101            "fbx" => Some(Self::Fbx {}),
1102            "gltf" => Some(Self::Gltf {}),
1103            "sldprt" => Some(Self::Sldprt {}),
1104            "step" => Some(Self::Step {}),
1105            "stl" => {
1106                let_field_of!(obj, coords?);
1107                let_field_of!(obj, units);
1108                Some(Self::Stl { coords, units })
1109            }
1110            "obj" => {
1111                let_field_of!(obj, coords?);
1112                let_field_of!(obj, units);
1113                Some(Self::Obj { coords, units })
1114            }
1115            "ply" => {
1116                let_field_of!(obj, coords?);
1117                let_field_of!(obj, units);
1118                Some(Self::Ply { coords, units })
1119            }
1120            _ => None,
1121        }
1122    }
1123}
1124
1125impl<'a> FromKclValue<'a> for super::sketch::AngledLineThatIntersectsData {
1126    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1127        let obj = arg.as_object()?;
1128        let_field_of!(obj, angle);
1129        let_field_of!(obj, intersect_tag "intersectTag");
1130        let_field_of!(obj, offset?);
1131        Some(Self {
1132            angle,
1133            intersect_tag,
1134            offset,
1135        })
1136    }
1137}
1138
1139impl<'a> FromKclValue<'a> for super::shapes::PolygonData {
1140    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1141        let obj = arg.as_object()?;
1142        let_field_of!(obj, radius);
1143        let_field_of!(obj, num_sides "numSides");
1144        let_field_of!(obj, center);
1145        let_field_of!(obj, inscribed);
1146        let polygon_type = if inscribed {
1147            PolygonType::Inscribed
1148        } else {
1149            PolygonType::Circumscribed
1150        };
1151        Some(Self {
1152            radius,
1153            num_sides,
1154            center,
1155            polygon_type,
1156            inscribed,
1157        })
1158    }
1159}
1160
1161impl<'a> FromKclValue<'a> for crate::std::polar::PolarCoordsData {
1162    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1163        let obj = arg.as_object()?;
1164        let_field_of!(obj, angle);
1165        let_field_of!(obj, length);
1166        Some(Self { angle, length })
1167    }
1168}
1169
1170impl<'a> FromKclValue<'a> for crate::execution::Plane {
1171    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1172        arg.as_plane().cloned()
1173    }
1174}
1175
1176impl<'a> FromKclValue<'a> for crate::execution::PlaneType {
1177    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1178        let plane_type = match arg.as_str()? {
1179            "XY" | "xy" => Self::XY,
1180            "XZ" | "xz" => Self::XZ,
1181            "YZ" | "yz" => Self::YZ,
1182            "Custom" => Self::Custom,
1183            _ => return None,
1184        };
1185        Some(plane_type)
1186    }
1187}
1188
1189impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::units::UnitLength {
1190    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1191        let s = arg.as_str()?;
1192        s.parse().ok()
1193    }
1194}
1195
1196impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::System {
1197    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1198        let obj = arg.as_object()?;
1199        let_field_of!(obj, forward);
1200        let_field_of!(obj, up);
1201        Some(Self { forward, up })
1202    }
1203}
1204
1205impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::AxisDirectionPair {
1206    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1207        let obj = arg.as_object()?;
1208        let_field_of!(obj, axis);
1209        let_field_of!(obj, direction);
1210        Some(Self { axis, direction })
1211    }
1212}
1213
1214impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Axis {
1215    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1216        let s = arg.as_str()?;
1217        match s {
1218            "y" => Some(Self::Y),
1219            "z" => Some(Self::Z),
1220            _ => None,
1221        }
1222    }
1223}
1224
1225impl<'a> FromKclValue<'a> for PolygonType {
1226    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1227        let s = arg.as_str()?;
1228        match s {
1229            "inscribed" => Some(Self::Inscribed),
1230            _ => Some(Self::Circumscribed),
1231        }
1232    }
1233}
1234
1235impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Direction {
1236    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1237        let s = arg.as_str()?;
1238        match s {
1239            "positive" => Some(Self::Positive),
1240            "negative" => Some(Self::Negative),
1241            _ => None,
1242        }
1243    }
1244}
1245
1246impl<'a> FromKclValue<'a> for super::sketch::BezierData {
1247    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1248        let obj = arg.as_object()?;
1249        let_field_of!(obj, to);
1250        let_field_of!(obj, control1);
1251        let_field_of!(obj, control2);
1252        Some(Self { to, control1, control2 })
1253    }
1254}
1255
1256impl<'a> FromKclValue<'a> for super::helix::HelixRevolutionsData {
1257    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1258        let obj = arg.as_object()?;
1259        let_field_of!(obj, revolutions);
1260        let_field_of!(obj, length?);
1261        let_field_of!(obj, ccw?);
1262        let ccw = ccw.unwrap_or_default();
1263        let angle_start = obj.get("angleStart")?.as_f64()?;
1264        Some(Self {
1265            revolutions,
1266            angle_start,
1267            ccw,
1268            length,
1269        })
1270    }
1271}
1272
1273impl<'a> FromKclValue<'a> for FaceTag {
1274    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1275        let case1 = || match arg.as_str() {
1276            Some("start" | "START") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::Start)),
1277            Some("end" | "END") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::End)),
1278            _ => None,
1279        };
1280        let case2 = || {
1281            let tag = TagIdentifier::from_kcl_val(arg)?;
1282            Some(Self::Tag(Box::new(tag)))
1283        };
1284        case1().or_else(case2)
1285    }
1286}
1287
1288impl<'a> FromKclValue<'a> for super::sketch::AngledLineToData {
1289    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1290        // Deserialize from an {angle, to} object.
1291        let case1 = || {
1292            let obj = arg.as_object()?;
1293            let_field_of!(obj, to);
1294            let_field_of!(obj, angle);
1295            Some(Self { angle, to })
1296        };
1297        // Deserialize from an [angle, to] array.
1298        let case2 = || {
1299            let [angle, to] = arg.as_point2d()?;
1300            Some(Self { angle, to })
1301        };
1302        case1().or_else(case2)
1303    }
1304}
1305
1306impl<'a> FromKclValue<'a> for super::sketch::ArcData {
1307    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1308        let obj = arg.as_object()?;
1309        let_field_of!(obj, radius);
1310        let case1 = || {
1311            let angle_start = obj.get("angleStart")?.as_f64()?;
1312            let angle_end = obj.get("angleEnd")?.as_f64()?;
1313            Some(Self::AnglesAndRadius {
1314                angle_start,
1315                angle_end,
1316                radius,
1317            })
1318        };
1319        let case2 = || {
1320            let obj = arg.as_object()?;
1321            let_field_of!(obj, to);
1322            let_field_of!(obj, center);
1323            Some(Self::CenterToRadius { center, to, radius })
1324        };
1325        case1().or_else(case2)
1326    }
1327}
1328
1329impl<'a> FromKclValue<'a> for super::sketch::ArcToData {
1330    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1331        let obj = arg.as_object()?;
1332        let_field_of!(obj, end);
1333        let_field_of!(obj, interior);
1334        Some(Self { end, interior })
1335    }
1336}
1337
1338impl<'a> FromKclValue<'a> for super::sketch::TangentialArcData {
1339    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1340        let obj = arg.as_object()?;
1341        let_field_of!(obj, radius);
1342        let_field_of!(obj, offset);
1343        Some(Self::RadiusAndOffset { radius, offset })
1344    }
1345}
1346
1347impl<'a> FromKclValue<'a> for crate::execution::Point3d {
1348    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1349        // Case 1: object with x/y/z fields
1350        if let Some(obj) = arg.as_object() {
1351            let_field_of!(obj, x);
1352            let_field_of!(obj, y);
1353            let_field_of!(obj, z);
1354            return Some(Self { x, y, z });
1355        }
1356        // Case 2: Array of 3 numbers.
1357        let [x, y, z]: [f64; 3] = FromKclValue::from_kcl_val(arg)?;
1358        Some(Self { x, y, z })
1359    }
1360}
1361
1362impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
1363    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1364        // Case 0: actual plane
1365        if let KclValue::Plane { value } = arg {
1366            return Some(Self::Plane {
1367                origin: value.origin,
1368                x_axis: value.x_axis,
1369                y_axis: value.y_axis,
1370                z_axis: value.z_axis,
1371            });
1372        }
1373        // Case 1: predefined plane
1374        if let Some(s) = arg.as_str() {
1375            return match s {
1376                "XY" | "xy" => Some(Self::XY),
1377                "-XY" | "-xy" => Some(Self::NegXY),
1378                "XZ" | "xz" => Some(Self::XZ),
1379                "-XZ" | "-xz" => Some(Self::NegXZ),
1380                "YZ" | "yz" => Some(Self::YZ),
1381                "-YZ" | "-yz" => Some(Self::NegYZ),
1382                _ => None,
1383            };
1384        }
1385        // Case 2: custom plane
1386        let obj = arg.as_object()?;
1387        let_field_of!(obj, plane, &KclObjectFields);
1388        let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val)?;
1389        let x_axis = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?;
1390        let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val)?;
1391        let z_axis = plane.get("zAxis").and_then(FromKclValue::from_kcl_val)?;
1392        Some(Self::Plane {
1393            origin,
1394            x_axis,
1395            y_axis,
1396            z_axis,
1397        })
1398    }
1399}
1400
1401impl<'a> FromKclValue<'a> for crate::execution::ExtrudePlane {
1402    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1403        let obj = arg.as_object()?;
1404        let_field_of!(obj, face_id "faceId");
1405        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
1406        let_field_of!(obj, geo_meta "geoMeta");
1407        Some(Self { face_id, tag, geo_meta })
1408    }
1409}
1410
1411impl<'a> FromKclValue<'a> for crate::execution::ExtrudeArc {
1412    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1413        let obj = arg.as_object()?;
1414        let_field_of!(obj, face_id "faceId");
1415        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
1416        let_field_of!(obj, geo_meta "geoMeta");
1417        Some(Self { face_id, tag, geo_meta })
1418    }
1419}
1420
1421impl<'a> FromKclValue<'a> for crate::execution::GeoMeta {
1422    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1423        let obj = arg.as_object()?;
1424        let_field_of!(obj, id);
1425        let_field_of!(obj, source_range "sourceRange");
1426        Some(Self {
1427            id,
1428            metadata: Metadata { source_range },
1429        })
1430    }
1431}
1432
1433impl<'a> FromKclValue<'a> for crate::execution::ChamferSurface {
1434    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1435        let obj = arg.as_object()?;
1436        let_field_of!(obj, face_id "faceId");
1437        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
1438        let_field_of!(obj, geo_meta "geoMeta");
1439        Some(Self { face_id, tag, geo_meta })
1440    }
1441}
1442
1443impl<'a> FromKclValue<'a> for crate::execution::FilletSurface {
1444    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1445        let obj = arg.as_object()?;
1446        let_field_of!(obj, face_id "faceId");
1447        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
1448        let_field_of!(obj, geo_meta "geoMeta");
1449        Some(Self { face_id, tag, geo_meta })
1450    }
1451}
1452
1453impl<'a> FromKclValue<'a> for ExtrudeSurface {
1454    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1455        let case1 = crate::execution::ExtrudePlane::from_kcl_val;
1456        let case2 = crate::execution::ExtrudeArc::from_kcl_val;
1457        let case3 = crate::execution::ChamferSurface::from_kcl_val;
1458        let case4 = crate::execution::FilletSurface::from_kcl_val;
1459        case1(arg)
1460            .map(Self::ExtrudePlane)
1461            .or_else(|| case2(arg).map(Self::ExtrudeArc))
1462            .or_else(|| case3(arg).map(Self::Chamfer))
1463            .or_else(|| case4(arg).map(Self::Fillet))
1464    }
1465}
1466
1467impl<'a> FromKclValue<'a> for crate::execution::EdgeCut {
1468    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1469        let obj = arg.as_object()?;
1470        let_field_of!(obj, typ "type");
1471        let tag = Box::new(obj.get("tag").and_then(FromKclValue::from_kcl_val));
1472        let_field_of!(obj, edge_id "edgeId");
1473        let_field_of!(obj, id);
1474        match typ {
1475            "fillet" => {
1476                let_field_of!(obj, radius);
1477                Some(Self::Fillet {
1478                    edge_id,
1479                    tag,
1480                    id,
1481                    radius,
1482                })
1483            }
1484            "chamfer" => {
1485                let_field_of!(obj, length);
1486                Some(Self::Chamfer {
1487                    id,
1488                    length,
1489                    edge_id,
1490                    tag,
1491                })
1492            }
1493            _ => None,
1494        }
1495    }
1496}
1497
1498macro_rules! impl_from_kcl_for_vec {
1499    ($typ:path) => {
1500        impl<'a> FromKclValue<'a> for Vec<$typ> {
1501            fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1502                arg.as_array()?
1503                    .iter()
1504                    .map(|value| FromKclValue::from_kcl_val(value))
1505                    .collect::<Option<_>>()
1506            }
1507        }
1508    };
1509}
1510
1511impl_from_kcl_for_vec!(FaceTag);
1512impl_from_kcl_for_vec!(crate::execution::EdgeCut);
1513impl_from_kcl_for_vec!(crate::execution::Metadata);
1514impl_from_kcl_for_vec!(super::fillet::EdgeReference);
1515impl_from_kcl_for_vec!(ExtrudeSurface);
1516
1517impl<'a> FromKclValue<'a> for SourceRange {
1518    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1519        let KclValue::MixedArray { value, meta: _ } = arg else {
1520            return None;
1521        };
1522        if value.len() != 3 {
1523            return None;
1524        }
1525        let v0 = value.first()?;
1526        let v1 = value.get(1)?;
1527        let v2 = value.get(2)?;
1528        Some(SourceRange::new(
1529            v0.as_usize()?,
1530            v1.as_usize()?,
1531            ModuleId::from_usize(v2.as_usize()?),
1532        ))
1533    }
1534}
1535
1536impl<'a> FromKclValue<'a> for crate::execution::Metadata {
1537    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1538        FromKclValue::from_kcl_val(arg).map(|sr| Self { source_range: sr })
1539    }
1540}
1541
1542impl<'a> FromKclValue<'a> for crate::execution::Solid {
1543    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1544        arg.as_solid().cloned()
1545    }
1546}
1547
1548impl<'a> FromKclValue<'a> for crate::execution::SolidOrSketchOrImportedGeometry {
1549    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1550        match arg {
1551            KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])),
1552            KclValue::Sketch { value } => Some(Self::SketchSet(vec![(**value).clone()])),
1553            KclValue::HomArray { value, .. } => {
1554                let mut solids = vec![];
1555                let mut sketches = vec![];
1556                for item in value {
1557                    match item {
1558                        KclValue::Solid { value } => solids.push((**value).clone()),
1559                        KclValue::Sketch { value } => sketches.push((**value).clone()),
1560                        _ => return None,
1561                    }
1562                }
1563                if !solids.is_empty() {
1564                    Some(Self::SolidSet(solids))
1565                } else {
1566                    Some(Self::SketchSet(sketches))
1567                }
1568            }
1569            KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
1570            _ => None,
1571        }
1572    }
1573}
1574
1575impl<'a> FromKclValue<'a> for super::sketch::SketchData {
1576    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1577        // Order is critical since PlaneData is a subset of Plane.
1578        let case1 = crate::execution::Plane::from_kcl_val;
1579        let case2 = super::sketch::PlaneData::from_kcl_val;
1580        let case3 = crate::execution::Solid::from_kcl_val;
1581        let case4 = <Vec<Solid>>::from_kcl_val;
1582        case1(arg)
1583            .map(Box::new)
1584            .map(Self::Plane)
1585            .or_else(|| case2(arg).map(Self::PlaneOrientation))
1586            .or_else(|| case3(arg).map(Box::new).map(Self::Solid))
1587            .or_else(|| case4(arg).map(|v| Box::new(v[0].clone())).map(Self::Solid))
1588    }
1589}
1590
1591impl<'a> FromKclValue<'a> for super::axis_or_reference::AxisAndOrigin2d {
1592    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1593        // Case 1: predefined planes.
1594        if let Some(s) = arg.as_str() {
1595            return match s {
1596                "X" | "x" => Some(Self::X),
1597                "Y" | "y" => Some(Self::Y),
1598                "-X" | "-x" => Some(Self::NegX),
1599                "-Y" | "-y" => Some(Self::NegY),
1600                _ => None,
1601            };
1602        }
1603        // Case 2: custom planes.
1604        let obj = arg.as_object()?;
1605        let_field_of!(obj, custom, &KclObjectFields);
1606        let_field_of!(custom, origin);
1607        let_field_of!(custom, axis);
1608        Some(Self::Custom { axis, origin })
1609    }
1610}
1611
1612impl<'a> FromKclValue<'a> for super::axis_or_reference::AxisAndOrigin3d {
1613    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1614        // Case 1: predefined planes.
1615        if let Some(s) = arg.as_str() {
1616            return match s {
1617                "X" | "x" => Some(Self::X),
1618                "Y" | "y" => Some(Self::Y),
1619                "Z" | "z" => Some(Self::Z),
1620                "-X" | "-x" => Some(Self::NegX),
1621                "-Y" | "-y" => Some(Self::NegY),
1622                "-Z" | "-z" => Some(Self::NegZ),
1623                _ => None,
1624            };
1625        }
1626        // Case 2: custom planes.
1627        let obj = arg.as_object()?;
1628        let_field_of!(obj, custom, &KclObjectFields);
1629        let_field_of!(custom, origin);
1630        let_field_of!(custom, axis);
1631        Some(Self::Custom { axis, origin })
1632    }
1633}
1634
1635impl<'a> FromKclValue<'a> for super::fillet::EdgeReference {
1636    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1637        let id = arg.as_uuid().map(Self::Uuid);
1638        let tag = || TagIdentifier::from_kcl_val(arg).map(Box::new).map(Self::Tag);
1639        id.or_else(tag)
1640    }
1641}
1642
1643impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis2dOrEdgeReference {
1644    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1645        let case1 = super::axis_or_reference::AxisAndOrigin2d::from_kcl_val;
1646        let case2 = super::fillet::EdgeReference::from_kcl_val;
1647        case1(arg).map(Self::Axis).or_else(|| case2(arg).map(Self::Edge))
1648    }
1649}
1650
1651impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrEdgeReference {
1652    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1653        let case1 = super::axis_or_reference::AxisAndOrigin3d::from_kcl_val;
1654        let case2 = super::fillet::EdgeReference::from_kcl_val;
1655        case1(arg).map(Self::Axis).or_else(|| case2(arg).map(Self::Edge))
1656    }
1657}
1658
1659impl<'a> FromKclValue<'a> for super::mirror::Mirror2dData {
1660    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1661        let obj = arg.as_object()?;
1662        let_field_of!(obj, axis);
1663        Some(Self { axis })
1664    }
1665}
1666
1667impl<'a> FromKclValue<'a> for super::sketch::AngledLineData {
1668    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1669        let case1 = |arg: &KclValue| {
1670            let obj = arg.as_object()?;
1671            let_field_of!(obj, angle);
1672            let_field_of!(obj, length);
1673            Some(Self::AngleAndLengthNamed { angle, length })
1674        };
1675        let case2 = |arg: &KclValue| {
1676            let array = arg.as_array()?;
1677            let ang = array.first()?.as_f64()?;
1678            let len = array.get(1)?.as_f64()?;
1679            Some(Self::AngleAndLengthPair([ang, len]))
1680        };
1681        case1(arg).or_else(|| case2(arg))
1682    }
1683}
1684
1685impl<'a> FromKclValue<'a> for i64 {
1686    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1687        match arg {
1688            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
1689            _ => None,
1690        }
1691    }
1692}
1693
1694impl<'a> FromKclValue<'a> for &'a str {
1695    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1696        let KclValue::String { value, meta: _ } = arg else {
1697            return None;
1698        };
1699        Some(value)
1700    }
1701}
1702
1703impl<'a> FromKclValue<'a> for &'a KclObjectFields {
1704    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1705        let KclValue::Object { value, meta: _ } = arg else {
1706            return None;
1707        };
1708        Some(value)
1709    }
1710}
1711
1712impl<'a> FromKclValue<'a> for uuid::Uuid {
1713    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1714        let KclValue::Uuid { value, meta: _ } = arg else {
1715            return None;
1716        };
1717        Some(*value)
1718    }
1719}
1720
1721impl<'a> FromKclValue<'a> for u32 {
1722    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1723        match arg {
1724            KclValue::Number { value, .. } => crate::try_f64_to_u32(*value),
1725            _ => None,
1726        }
1727    }
1728}
1729
1730impl<'a> FromKclValue<'a> for NonZeroU32 {
1731    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1732        u32::from_kcl_val(arg).and_then(|x| x.try_into().ok())
1733    }
1734}
1735
1736impl<'a> FromKclValue<'a> for u64 {
1737    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1738        match arg {
1739            KclValue::Number { value, .. } => crate::try_f64_to_u64(*value),
1740            _ => None,
1741        }
1742    }
1743}
1744impl<'a> FromKclValue<'a> for f64 {
1745    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1746        match arg {
1747            KclValue::Number { value, .. } => Some(*value),
1748            _ => None,
1749        }
1750    }
1751}
1752impl<'a> FromKclValue<'a> for TyF64 {
1753    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1754        match arg {
1755            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, ty.clone())),
1756            _ => None,
1757        }
1758    }
1759}
1760
1761impl<'a> FromKclValue<'a> for Sketch {
1762    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1763        let KclValue::Sketch { value } = arg else {
1764            return None;
1765        };
1766        Some(value.as_ref().to_owned())
1767    }
1768}
1769
1770impl<'a> FromKclValue<'a> for Helix {
1771    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1772        let KclValue::Helix { value } = arg else {
1773            return None;
1774        };
1775        Some(value.as_ref().to_owned())
1776    }
1777}
1778
1779impl<'a> FromKclValue<'a> for SweepPath {
1780    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1781        let case1 = Sketch::from_kcl_val;
1782        let case2 = <Vec<Sketch>>::from_kcl_val;
1783        let case3 = Helix::from_kcl_val;
1784        case1(arg)
1785            .map(Self::Sketch)
1786            .or_else(|| case2(arg).map(|arg0: Vec<Sketch>| Self::Sketch(arg0[0].clone())))
1787            .or_else(|| case3(arg).map(|arg0: Helix| Self::Helix(Box::new(arg0))))
1788    }
1789}
1790impl<'a> FromKclValue<'a> for String {
1791    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1792        let KclValue::String { value, meta: _ } = arg else {
1793            return None;
1794        };
1795        Some(value.to_owned())
1796    }
1797}
1798impl<'a> FromKclValue<'a> for crate::parsing::ast::types::KclNone {
1799    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1800        let KclValue::KclNone { value, meta: _ } = arg else {
1801            return None;
1802        };
1803        Some(value.to_owned())
1804    }
1805}
1806impl<'a> FromKclValue<'a> for bool {
1807    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1808        let KclValue::Bool { value, meta: _ } = arg else {
1809            return None;
1810        };
1811        Some(*value)
1812    }
1813}
1814
1815impl<'a> FromKclValue<'a> for Box<Solid> {
1816    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1817        let KclValue::Solid { value } = arg else {
1818            return None;
1819        };
1820        Some(value.to_owned())
1821    }
1822}
1823
1824impl<'a> FromKclValue<'a> for Vec<Solid> {
1825    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1826        let KclValue::HomArray { value, .. } = arg else {
1827            return None;
1828        };
1829        value.iter().map(Solid::from_kcl_val).collect()
1830    }
1831}
1832
1833impl<'a> FromKclValue<'a> for Vec<Sketch> {
1834    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1835        let KclValue::HomArray { value, .. } = arg else {
1836            return None;
1837        };
1838        value.iter().map(Sketch::from_kcl_val).collect()
1839    }
1840}
1841
1842impl<'a> FromKclValue<'a> for &'a FunctionSource {
1843    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1844        arg.get_function()
1845    }
1846}
1847
1848impl<'a> FromKclValue<'a> for SketchOrSurface {
1849    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1850        match arg {
1851            KclValue::Sketch { value: sg } => Some(Self::Sketch(sg.to_owned())),
1852            KclValue::Plane { value } => Some(Self::SketchSurface(SketchSurface::Plane(value.clone()))),
1853            KclValue::Face { value } => Some(Self::SketchSurface(SketchSurface::Face(value.clone()))),
1854            _ => None,
1855        }
1856    }
1857}
1858impl<'a> FromKclValue<'a> for SketchSurface {
1859    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1860        match arg {
1861            KclValue::Plane { value } => Some(Self::Plane(value.clone())),
1862            KclValue::Face { value } => Some(Self::Face(value.clone())),
1863            _ => None,
1864        }
1865    }
1866}
1867
1868impl From<Args> for Metadata {
1869    fn from(value: Args) -> Self {
1870        Self {
1871            source_range: value.source_range,
1872        }
1873    }
1874}
1875
1876impl From<Args> for Vec<Metadata> {
1877    fn from(value: Args) -> Self {
1878        vec![Metadata {
1879            source_range: value.source_range,
1880        }]
1881    }
1882}