Files
modeling-app/rust/kcl-lib/src/std/args.rs
2025-03-21 22:26:23 +13:00

1894 lines
62 KiB
Rust

use std::{any::type_name, collections::HashMap, num::NonZeroU32};
use anyhow::Result;
use kcmc::{
websocket::{ModelingCmdReq, OkWebSocketResponseData},
ModelingCmd,
};
use kittycad_modeling_cmds as kcmc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
kcl_value::FunctionSource,
types::{NumericType, PrimitiveType, RuntimeType},
ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Sketch, SketchSurface,
Solid, TagIdentifier,
},
parsing::ast::types::TagNode,
source_range::SourceRange,
std::{
shapes::{PolygonType, SketchOrSurface},
sketch::FaceTag,
sweep::SweepPath,
},
ModuleId,
};
#[derive(Debug, Clone)]
pub struct Arg {
/// The evaluated argument.
pub value: KclValue,
/// The source range of the unevaluated argument.
pub source_range: SourceRange,
}
impl Arg {
pub fn new(value: KclValue, source_range: SourceRange) -> Self {
Self { value, source_range }
}
pub fn synthetic(value: KclValue) -> Self {
Self {
value,
source_range: SourceRange::synthetic(),
}
}
pub fn source_ranges(&self) -> Vec<SourceRange> {
vec![self.source_range]
}
}
#[derive(Debug, Clone, Default)]
pub struct KwArgs {
/// Unlabeled keyword args. Currently only the first arg can be unlabeled.
pub unlabeled: Option<Arg>,
/// Labeled args.
pub labeled: HashMap<String, Arg>,
}
impl KwArgs {
/// How many arguments are there?
pub fn len(&self) -> usize {
self.labeled.len() + if self.unlabeled.is_some() { 1 } else { 0 }
}
/// Are there no arguments?
pub fn is_empty(&self) -> bool {
self.labeled.len() == 0 && self.unlabeled.is_none()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct TyF64 {
pub n: f64,
pub ty: NumericType,
}
impl TyF64 {
pub fn new(n: f64, ty: NumericType) -> Self {
Self { n, ty }
}
pub fn count(n: f64) -> Self {
Self {
n,
ty: NumericType::count(),
}
}
pub fn map(mut self, n: f64) -> Self {
self.n = n;
self
}
}
#[derive(Debug, Clone)]
pub struct Args {
/// Positional args.
pub args: Vec<Arg>,
/// Keyword arguments
pub kw_args: KwArgs,
pub source_range: SourceRange,
pub ctx: ExecutorContext,
/// If this call happens inside a pipe (|>) expression, this holds the LHS of that |>.
/// Otherwise it's None.
pipe_value: Option<Arg>,
}
impl Args {
pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
Self {
args,
kw_args: Default::default(),
source_range,
ctx,
pipe_value,
}
}
/// Collect the given keyword arguments.
pub fn new_kw(kw_args: KwArgs, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
Self {
args: Default::default(),
kw_args,
source_range,
ctx,
pipe_value,
}
}
/// Get a keyword argument. If not set, returns None.
pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Result<Option<T>, KclError>
where
T: FromKclValue<'a>,
{
let Some(arg) = self.kw_args.labeled.get(label) else {
return Ok(None);
};
T::from_kcl_val(&arg.value).map(Some).ok_or_else(|| {
KclError::Type(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!(
"The arg {label} was given, but it was the wrong type. It should be type {} but it was {}",
type_name::<T>(),
arg.value.human_friendly_type(),
),
})
})
}
/// Get a keyword argument. If not set, returns Err.
pub(crate) fn get_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
where
T: FromKclValue<'a>,
{
self.get_kw_arg_opt(label)?.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a keyword argument '{label}'"),
})
})
}
pub(crate) fn get_kw_arg_typed<T>(
&self,
label: &str,
ty: &RuntimeType,
exec_state: &mut ExecState,
) -> Result<T, KclError>
where
T: for<'a> FromKclValue<'a>,
{
let Some(arg) = self.kw_args.labeled.get(label) else {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a keyword argument '{label}'"),
}));
};
let arg = arg.value.coerce(ty, exec_state).ok_or_else(|| {
let actual_type_name = arg.value.human_friendly_type();
let msg_base = format!(
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
ty.human_friendly_type(),
);
let suggestion = match (ty, actual_type_name) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
),
(RuntimeType::Array(t, _), "Sketch") if **t == RuntimeType::Primitive(PrimitiveType::Solid) => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
),
_ => None,
};
let message = match suggestion {
None => msg_base,
Some(sugg) => format!("{msg_base}. {sugg}"),
};
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message,
})
})?;
// TODO unnecessary cloning
Ok(T::from_kcl_val(&arg).unwrap())
}
/// Get a labelled keyword arg, check it's an array, and return all items in the array
/// plus their source range.
pub(crate) fn kw_arg_array_and_source<'a, T>(&'a self, label: &str) -> Result<Vec<(T, SourceRange)>, KclError>
where
T: FromKclValue<'a>,
{
let Some(arg) = self.kw_args.labeled.get(label) else {
let err = KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a keyword argument '{label}'"),
});
return Err(err);
};
let Some(array) = arg.value.as_array() else {
let err = KclError::Semantic(KclErrorDetails {
source_ranges: vec![arg.source_range],
message: format!(
"Expected an array of {} but found {}",
type_name::<T>(),
arg.value.human_friendly_type()
),
});
return Err(err);
};
array
.iter()
.map(|item| {
let source = SourceRange::from(item);
let val = FromKclValue::from_kcl_val(item).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message: format!(
"Expected a {} but found {}",
type_name::<T>(),
arg.value.human_friendly_type()
),
})
})?;
Ok((val, source))
})
.collect::<Result<Vec<_>, _>>()
}
/// Get the unlabeled keyword argument. If not set, returns None.
pub(crate) fn unlabeled_kw_arg_unconverted(&self) -> Option<&Arg> {
self.kw_args
.unlabeled
.as_ref()
.or(self.args.first())
.or(self.pipe_value.as_ref())
}
/// Get the unlabeled keyword argument. If not set, returns Err. If it
/// can't be converted to the given type, returns Err.
pub(crate) fn get_unlabeled_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
where
T: FromKclValue<'a>,
{
let arg = self
.unlabeled_kw_arg_unconverted()
.ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
}))?;
T::from_kcl_val(&arg.value).ok_or_else(|| {
let expected_type_name = tynm::type_name::<T>();
let actual_type_name = arg.value.human_friendly_type();
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}");
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message,
})
})
}
/// Get the unlabeled keyword argument. If not set, returns Err. If it
/// can't be converted to the given type, returns Err.
pub(crate) fn get_unlabeled_kw_arg_typed<T>(
&self,
label: &str,
ty: &RuntimeType,
exec_state: &mut ExecState,
) -> Result<T, KclError>
where
T: for<'a> FromKclValue<'a>,
{
let arg = self
.unlabeled_kw_arg_unconverted()
.ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
}))?;
let arg = arg.value.coerce(ty, exec_state).ok_or_else(|| {
let actual_type_name = arg.value.human_friendly_type();
let msg_base = format!(
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
ty.human_friendly_type(),
);
let suggestion = match (ty, actual_type_name) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
),
(RuntimeType::Array(ty, _), "Sketch") if **ty == RuntimeType::Primitive(PrimitiveType::Solid) => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
),
_ => None,
};
let message = match suggestion {
None => msg_base,
Some(sugg) => format!("{msg_base}. {sugg}"),
};
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message,
})
})?;
// TODO unnecessary cloning
Ok(T::from_kcl_val(&arg).unwrap())
}
// Add a modeling command to the batch but don't fire it right away.
pub(crate) async fn batch_modeling_cmd(
&self,
id: uuid::Uuid,
cmd: ModelingCmd,
) -> Result<(), crate::errors::KclError> {
self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await
}
// Add multiple modeling commands to the batch but don't fire them right away.
pub(crate) async fn batch_modeling_cmds(&self, cmds: &[ModelingCmdReq]) -> Result<(), crate::errors::KclError> {
self.ctx.engine.batch_modeling_cmds(self.source_range, cmds).await
}
// Add a modeling commandSolid> to the batch that gets executed at the end of the file.
// This is good for something like fillet or chamfer where the engine would
// eat the path id if we executed it right away.
pub(crate) async fn batch_end_cmd(&self, id: uuid::Uuid, cmd: ModelingCmd) -> Result<(), crate::errors::KclError> {
self.ctx.engine.batch_end_cmd(id, self.source_range, &cmd).await
}
/// Send the modeling cmd and wait for the response.
pub(crate) async fn send_modeling_cmd(
&self,
id: uuid::Uuid,
cmd: ModelingCmd,
) -> Result<OkWebSocketResponseData, KclError> {
self.ctx.engine.send_modeling_cmd(id, self.source_range, &cmd).await
}
fn get_tag_info_from_memory<'a, 'e>(
&'a self,
exec_state: &'e mut ExecState,
tag: &'a TagIdentifier,
) -> Result<&'e crate::execution::TagEngineInfo, KclError> {
if let (epoch, KclValue::TagIdentifier(t)) =
exec_state.stack().get_from_call_stack(&tag.value, self.source_range)?
{
let info = t.get_info(epoch).ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not have engine info", tag.value),
source_ranges: vec![self.source_range],
})
})?;
Ok(info)
} else {
Err(KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not exist", tag.value),
source_ranges: vec![self.source_range],
}))
}
}
pub(crate) fn get_tag_engine_info<'a, 'e>(
&'a self,
exec_state: &'e mut ExecState,
tag: &'a TagIdentifier,
) -> Result<&'a crate::execution::TagEngineInfo, KclError>
where
'e: 'a,
{
if let Some(info) = tag.get_cur_info() {
return Ok(info);
}
self.get_tag_info_from_memory(exec_state, tag)
}
fn get_tag_engine_info_check_surface<'a, 'e>(
&'a self,
exec_state: &'e mut ExecState,
tag: &'a TagIdentifier,
) -> Result<&'a crate::execution::TagEngineInfo, KclError>
where
'e: 'a,
{
if let Some(info) = tag.get_cur_info() {
if info.surface.is_some() {
return Ok(info);
}
}
self.get_tag_info_from_memory(exec_state, tag)
}
/// Flush just the fillets and chamfers for this specific SolidSet.
#[allow(clippy::vec_box)]
pub(crate) async fn flush_batch_for_solids(
&self,
exec_state: &mut ExecState,
solids: &[Solid],
) -> Result<(), KclError> {
// Make sure we don't traverse sketches more than once.
let mut traversed_sketches = Vec::new();
// Collect all the fillet/chamfer ids for the solids.
let mut ids = Vec::new();
for solid in solids {
// We need to traverse the solids that share the same sketch.
let sketch_id = solid.sketch.id;
if !traversed_sketches.contains(&sketch_id) {
// Find all the solids on the same shared sketch.
ids.extend(
exec_state
.stack()
.walk_call_stack()
.filter(|v| matches!(v, KclValue::Solid { value } if value.sketch.id == sketch_id))
.flat_map(|v| match v {
KclValue::Solid { value } => value.get_all_edge_cut_ids(),
_ => unreachable!(),
}),
);
traversed_sketches.push(sketch_id);
}
ids.extend(solid.get_all_edge_cut_ids());
}
// We can return early if there are no fillets or chamfers.
if ids.is_empty() {
return Ok(());
}
// We want to move these fillets and chamfers from batch_end to batch so they get executed
// before what ever we call next.
for id in ids {
// Pop it off the batch_end and add it to the batch.
let Some(item) = self.ctx.engine.batch_end().write().await.shift_remove(&id) else {
// It might be in the batch already.
continue;
};
// Add it to the batch.
self.ctx.engine.batch().write().await.push(item);
}
// Run flush.
// Yes, we do need to actually flush the batch here, or references will fail later.
self.ctx.engine.flush_batch(false, self.source_range).await?;
Ok(())
}
pub(crate) fn make_user_val_from_point(&self, p: [f64; 2]) -> Result<KclValue, KclError> {
let meta = Metadata {
source_range: self.source_range,
};
let x = KclValue::Number {
value: p[0],
meta: vec![meta],
ty: NumericType::Unknown,
};
let y = KclValue::Number {
value: p[1],
meta: vec![meta],
ty: NumericType::Unknown,
};
Ok(KclValue::MixedArray {
value: vec![x, y],
meta: vec![meta],
})
}
pub(crate) fn make_user_val_from_f64(&self, f: f64) -> KclValue {
KclValue::from_number(
f,
vec![Metadata {
source_range: self.source_range,
}],
)
}
pub(crate) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
KclValue::from_number_with_type(
f.n,
f.ty,
vec![Metadata {
source_range: self.source_range,
}],
)
}
pub(crate) fn make_user_val_from_f64_array(&self, f: Vec<f64>, ty: &NumericType) -> Result<KclValue, KclError> {
let array = f
.into_iter()
.map(|n| KclValue::Number {
value: n,
meta: vec![Metadata {
source_range: self.source_range,
}],
ty: ty.clone(),
})
.collect::<Vec<_>>();
Ok(KclValue::MixedArray {
value: array,
meta: vec![Metadata {
source_range: self.source_range,
}],
})
}
pub(crate) fn get_number(&self) -> Result<f64, KclError> {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_number_with_type(&self) -> Result<TyF64, KclError> {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_number_array(&self) -> Result<Vec<f64>, KclError> {
let numbers = self
.args
.iter()
.map(|arg| {
let Some(num) = f64::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message: format!("Expected a number but found {}", arg.value.human_friendly_type()),
}));
};
Ok(num)
})
.collect::<Result<_, _>>()?;
Ok(numbers)
}
pub(crate) fn get_number_array_with_types(&self) -> Result<Vec<TyF64>, KclError> {
let numbers = self
.args
.iter()
.map(|arg| {
let Some(num) = <TyF64>::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message: format!("Expected a number but found {}", arg.value.human_friendly_type()),
}));
};
Ok(num)
})
.collect::<Result<_, _>>()?;
Ok(numbers)
}
pub(crate) fn get_hypotenuse_leg(&self) -> Result<(f64, f64, NumericType), KclError> {
let numbers = self.get_number_array_with_types()?;
if numbers.len() != 2 {
return Err(KclError::Type(KclErrorDetails {
message: format!("Expected a number array of length 2, found `{:?}`", numbers),
source_ranges: vec![self.source_range],
}));
}
let mut numbers = numbers.into_iter();
let a = numbers.next().unwrap();
let b = numbers.next().unwrap();
let ty = a.ty.combine_eq(&b.ty);
Ok((a.n, b.n, ty))
}
pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> {
let Some(arg0) = self.args.first() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a sketch argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
let sarg = arg0
.value
.coerce(&RuntimeType::sketches(), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!(
"Expected an array of sketches, found {}",
arg0.value.human_friendly_type()
),
source_ranges: vec![self.source_range],
}))?;
let sketches = match sarg {
KclValue::HomArray { value, .. } => value.iter().map(|v| v.as_sketch().unwrap().clone()).collect(),
_ => unreachable!(),
};
let Some(arg1) = self.args.get(1) else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a second sketch argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
let sarg = arg1
.value
.coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!("Expected a sketch, found {}", arg1.value.human_friendly_type()),
source_ranges: vec![self.source_range],
}))?;
let sketch = match sarg {
KclValue::Sketch { value } => *value,
_ => unreachable!(),
};
Ok((sketches, sketch))
}
pub(crate) fn get_sketch(&self, exec_state: &mut ExecState) -> Result<Sketch, KclError> {
let Some(arg0) = self.args.first() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a sketch argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
let sarg = arg0
.value
.coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!("Expected a sketch, found {}", arg0.value.human_friendly_type()),
source_ranges: vec![self.source_range],
}))?;
match sarg {
KclValue::Sketch { value } => Ok(*value),
_ => unreachable!(),
}
}
pub(crate) fn get_data<'a, T>(&'a self) -> Result<T, KclError>
where
T: FromArgs<'a> + serde::de::DeserializeOwned,
{
FromArgs::from_args(self, 0)
}
pub(crate) fn get_import_data(&self) -> Result<(String, Option<crate::std::import::ImportFormat>), KclError> {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_optional_tag<'a, T>(&'a self) -> Result<(T, Option<FaceTag>), KclError>
where
T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
{
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_sketches<'a, T>(
&'a self,
exec_state: &mut ExecState,
) -> Result<(T, Vec<Sketch>), KclError>
where
T: serde::de::DeserializeOwned + FromArgs<'a>,
{
let data: T = FromArgs::from_args(self, 0)?;
let Some(arg1) = self.args.get(1) else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected one or more sketches for second argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
let sarg = arg1
.value
.coerce(&RuntimeType::sketches(), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!(
"Expected one or more sketches for second argument, found {}",
arg1.value.human_friendly_type()
),
source_ranges: vec![self.source_range],
}))?;
let sketches = match sarg {
KclValue::HomArray { value, .. } => value.iter().map(|v| v.as_sketch().unwrap().clone()).collect(),
_ => unreachable!(),
};
Ok((data, sketches))
}
pub(crate) fn get_data_and_sketch_and_tag<'a, T>(
&'a self,
exec_state: &mut ExecState,
) -> Result<(T, Sketch, Option<TagNode>), KclError>
where
T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
{
let data: T = FromArgs::from_args(self, 0)?;
let Some(arg1) = self.args.get(1) else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a sketch for second argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
let sarg = arg1
.value
.coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!(
"Expected a sketch for second argument, found {}",
arg1.value.human_friendly_type()
),
source_ranges: vec![self.source_range],
}))?;
let sketch = match sarg {
KclValue::Sketch { value } => *value,
_ => unreachable!(),
};
let tag: Option<TagNode> = FromArgs::from_args(self, 2)?;
Ok((data, sketch, tag))
}
pub(crate) fn get_data_and_sketch_surface<'a, T>(&'a self) -> Result<(T, SketchSurface, Option<TagNode>), KclError>
where
T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
{
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_solid<'a, T>(&'a self, exec_state: &mut ExecState) -> Result<(T, Box<Solid>), KclError>
where
T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
{
let data: T = FromArgs::from_args(self, 0)?;
let Some(arg1) = self.args.get(1) else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a solid for second argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
let sarg = arg1
.value
.coerce(&RuntimeType::Primitive(PrimitiveType::Solid), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!(
"Expected a solid for second argument, found {}",
arg1.value.human_friendly_type()
),
source_ranges: vec![self.source_range],
}))?;
let solid = match sarg {
KclValue::Solid { value } => value,
_ => unreachable!(),
};
Ok((data, solid))
}
pub(crate) fn get_tag_to_number_sketch(&self) -> Result<(TagIdentifier, f64, Sketch), KclError> {
FromArgs::from_args(self, 0)
}
pub(crate) async fn get_adjacent_face_to_tag(
&self,
exec_state: &mut ExecState,
tag: &TagIdentifier,
must_be_planar: bool,
) -> Result<uuid::Uuid, KclError> {
if tag.value.is_empty() {
return Err(KclError::Type(KclErrorDetails {
message: "Expected a non-empty tag for the face".to_string(),
source_ranges: vec![self.source_range],
}));
}
let engine_info = self.get_tag_engine_info_check_surface(exec_state, tag)?;
let surface = engine_info.surface.as_ref().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not have a surface", tag.value),
source_ranges: vec![self.source_range],
})
})?;
if let Some(face_from_surface) = match surface {
ExtrudeSurface::ExtrudePlane(extrude_plane) => {
if let Some(plane_tag) = &extrude_plane.tag {
if plane_tag.name == tag.value {
Some(Ok(extrude_plane.face_id))
} else {
None
}
} else {
None
}
}
// The must be planar check must be called before the arc check.
ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails {
message: format!("Tag `{}` is a non-planar surface", tag.value),
source_ranges: vec![self.source_range],
}))),
ExtrudeSurface::ExtrudeArc(extrude_arc) => {
if let Some(arc_tag) = &extrude_arc.tag {
if arc_tag.name == tag.value {
Some(Ok(extrude_arc.face_id))
} else {
None
}
} else {
None
}
}
ExtrudeSurface::Chamfer(chamfer) => {
if let Some(chamfer_tag) = &chamfer.tag {
if chamfer_tag.name == tag.value {
Some(Ok(chamfer.face_id))
} else {
None
}
} else {
None
}
}
// The must be planar check must be called before the fillet check.
ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails {
message: format!("Tag `{}` is a non-planar surface", tag.value),
source_ranges: vec![self.source_range],
}))),
ExtrudeSurface::Fillet(fillet) => {
if let Some(fillet_tag) = &fillet.tag {
if fillet_tag.name == tag.value {
Some(Ok(fillet.face_id))
} else {
None
}
} else {
None
}
}
} {
return face_from_surface;
}
// If we still haven't found the face, return an error.
Err(KclError::Type(KclErrorDetails {
message: format!("Expected a face with the tag `{}`", tag.value),
source_ranges: vec![self.source_range],
}))
}
pub(crate) fn get_polygon_args(
&self,
) -> Result<
(
crate::std::shapes::PolygonData,
crate::std::shapes::SketchOrSurface,
Option<TagNode>,
),
KclError,
> {
FromArgs::from_args(self, 0)
}
}
/// Types which impl this trait can be read out of the `Args` passed into a KCL function.
pub trait FromArgs<'a>: Sized {
/// Get this type from the args passed into a KCL function, at the given index in the argument list.
fn from_args(args: &'a Args, index: usize) -> Result<Self, KclError>;
}
/// Types which impl this trait can be extracted from a `KclValue`.
pub trait FromKclValue<'a>: Sized {
/// Try to convert a KclValue into this type.
fn from_kcl_val(arg: &'a KclValue) -> Option<Self>;
}
impl<'a> FromArgs<'a> for Vec<KclValue> {
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
let Some(arg) = args.args.get(i) else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected an argument at index {i}"),
source_ranges: vec![args.source_range],
}));
};
let KclValue::MixedArray { value: array, meta: _ } = &arg.value else {
let message = format!("Expected an array but found {}", arg.value.human_friendly_type());
return Err(KclError::Type(KclErrorDetails {
source_ranges: arg.source_ranges(),
message,
}));
};
Ok(array.to_owned())
}
}
impl<'a, T> FromArgs<'a> for T
where
T: FromKclValue<'a> + Sized,
{
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
let Some(arg) = args.args.get(i) else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected an argument at index {i}"),
source_ranges: vec![args.source_range],
}));
};
let Some(val) = T::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Argument at index {i} was supposed to be type {} but found {}",
type_name::<T>(),
arg.value.human_friendly_type(),
),
source_ranges: arg.source_ranges(),
}));
};
Ok(val)
}
}
impl<'a, T> FromArgs<'a> for Option<T>
where
T: FromKclValue<'a> + Sized,
{
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
let Some(arg) = args.args.get(i) else { return Ok(None) };
if crate::parsing::ast::types::KclNone::from_kcl_val(&arg.value).is_some() {
return Ok(None);
}
let Some(val) = T::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Argument at index {i} was supposed to be type Option<{}> but found {}",
type_name::<T>(),
arg.value.human_friendly_type()
),
source_ranges: arg.source_ranges(),
}));
};
Ok(Some(val))
}
}
impl<'a, A, B> FromArgs<'a> for (A, B)
where
A: FromArgs<'a>,
B: FromArgs<'a>,
{
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
let a = A::from_args(args, i)?;
let b = B::from_args(args, i + 1)?;
Ok((a, b))
}
}
impl<'a, A, B, C> FromArgs<'a> for (A, B, C)
where
A: FromArgs<'a>,
B: FromArgs<'a>,
C: FromArgs<'a>,
{
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
let a = A::from_args(args, i)?;
let b = B::from_args(args, i + 1)?;
let c = C::from_args(args, i + 2)?;
Ok((a, b, c))
}
}
impl<'a, A, B, C, D> FromArgs<'a> for (A, B, C, D)
where
A: FromArgs<'a>,
B: FromArgs<'a>,
C: FromArgs<'a>,
D: FromArgs<'a>,
{
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
let a = A::from_args(args, i)?;
let b = B::from_args(args, i + 1)?;
let c = C::from_args(args, i + 2)?;
let d = D::from_args(args, i + 3)?;
Ok((a, b, c, d))
}
}
impl<'a> FromKclValue<'a> for [f64; 2] {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
if value.len() != 2 {
return None;
}
let v0 = value.first()?;
let v1 = value.get(1)?;
let array = [v0.as_f64()?, v1.as_f64()?];
Some(array)
}
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for [usize; 3] {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
if value.len() != 3 {
return None;
}
let v0 = value.first()?;
let v1 = value.get(1)?;
let v2 = value.get(2)?;
let array = [v0.as_usize()?, v1.as_usize()?, v2.as_usize()?];
Some(array)
}
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for [f64; 3] {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
if value.len() != 3 {
return None;
}
let v0 = value.first()?;
let v1 = value.get(1)?;
let v2 = value.get(2)?;
let array = [v0.as_f64()?, v1.as_f64()?, v2.as_f64()?];
Some(array)
}
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for TagNode {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.get_tag_declarator().ok()
}
}
impl<'a> FromKclValue<'a> for TagIdentifier {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.get_tag_identifier().ok()
}
}
impl<'a> FromKclValue<'a> for Vec<TagIdentifier> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::HomArray { value, .. } => {
let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
Some(tags)
}
KclValue::MixedArray { value, .. } => {
let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
Some(tags)
}
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for KclValue {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
Some(arg.clone())
}
}
macro_rules! let_field_of {
// Optional field
($obj:ident, $field:ident?) => {
let $field = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val);
};
// Optional field but with a different string used as the key
($obj:ident, $field:ident? $key:literal) => {
let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val);
};
// Mandatory field, but with a different string used as the key.
($obj:ident, $field:ident $key:literal) => {
let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val)?;
};
// Mandatory field, optionally with a type annotation
($obj:ident, $field:ident $(, $annotation:ty)?) => {
let $field $(: $annotation)? = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val)?;
};
}
impl<'a> FromKclValue<'a> for crate::std::import::ImportFormat {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, typ "format");
match typ {
"fbx" => Some(Self::Fbx {}),
"gltf" => Some(Self::Gltf {}),
"sldprt" => Some(Self::Sldprt {}),
"step" => Some(Self::Step {}),
"stl" => {
let_field_of!(obj, coords?);
let_field_of!(obj, units);
Some(Self::Stl { coords, units })
}
"obj" => {
let_field_of!(obj, coords?);
let_field_of!(obj, units);
Some(Self::Obj { coords, units })
}
"ply" => {
let_field_of!(obj, coords?);
let_field_of!(obj, units);
Some(Self::Ply { coords, units })
}
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for super::sketch::AngledLineThatIntersectsData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, angle);
let_field_of!(obj, intersect_tag "intersectTag");
let_field_of!(obj, offset?);
Some(Self {
angle,
intersect_tag,
offset,
})
}
}
impl<'a> FromKclValue<'a> for super::shapes::PolygonData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, radius);
let_field_of!(obj, num_sides "numSides");
let_field_of!(obj, center);
let_field_of!(obj, inscribed);
let polygon_type = if inscribed {
PolygonType::Inscribed
} else {
PolygonType::Circumscribed
};
Some(Self {
radius,
num_sides,
center,
polygon_type,
inscribed,
})
}
}
impl<'a> FromKclValue<'a> for crate::std::polar::PolarCoordsData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, angle);
let_field_of!(obj, length);
Some(Self { angle, length })
}
}
impl<'a> FromKclValue<'a> for crate::execution::Plane {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.as_plane().cloned()
}
}
impl<'a> FromKclValue<'a> for crate::execution::PlaneType {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let plane_type = match arg.as_str()? {
"XY" | "xy" => Self::XY,
"XZ" | "xz" => Self::XZ,
"YZ" | "yz" => Self::YZ,
"Custom" => Self::Custom,
_ => return None,
};
Some(plane_type)
}
}
impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::units::UnitLength {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let s = arg.as_str()?;
s.parse().ok()
}
}
impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::System {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, forward);
let_field_of!(obj, up);
Some(Self { forward, up })
}
}
impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::AxisDirectionPair {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, axis);
let_field_of!(obj, direction);
Some(Self { axis, direction })
}
}
impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Axis {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let s = arg.as_str()?;
match s {
"y" => Some(Self::Y),
"z" => Some(Self::Z),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for PolygonType {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let s = arg.as_str()?;
match s {
"inscribed" => Some(Self::Inscribed),
_ => Some(Self::Circumscribed),
}
}
}
impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Direction {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let s = arg.as_str()?;
match s {
"positive" => Some(Self::Positive),
"negative" => Some(Self::Negative),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for super::sketch::BezierData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, to);
let_field_of!(obj, control1);
let_field_of!(obj, control2);
Some(Self { to, control1, control2 })
}
}
impl<'a> FromKclValue<'a> for super::helix::HelixRevolutionsData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, revolutions);
let_field_of!(obj, length?);
let_field_of!(obj, ccw?);
let ccw = ccw.unwrap_or_default();
let angle_start = obj.get("angleStart")?.as_f64()?;
Some(Self {
revolutions,
angle_start,
ccw,
length,
})
}
}
impl<'a> FromKclValue<'a> for FaceTag {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let case1 = || match arg.as_str() {
Some("start" | "START") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::Start)),
Some("end" | "END") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::End)),
_ => None,
};
let case2 = || {
let tag = TagIdentifier::from_kcl_val(arg)?;
Some(Self::Tag(Box::new(tag)))
};
case1().or_else(case2)
}
}
impl<'a> FromKclValue<'a> for super::sketch::AngledLineToData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
// Deserialize from an {angle, to} object.
let case1 = || {
let obj = arg.as_object()?;
let_field_of!(obj, to);
let_field_of!(obj, angle);
Some(Self { angle, to })
};
// Deserialize from an [angle, to] array.
let case2 = || {
let [angle, to] = arg.as_point2d()?;
Some(Self { angle, to })
};
case1().or_else(case2)
}
}
impl<'a> FromKclValue<'a> for super::sketch::ArcData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, radius);
let case1 = || {
let angle_start = obj.get("angleStart")?.as_f64()?;
let angle_end = obj.get("angleEnd")?.as_f64()?;
Some(Self::AnglesAndRadius {
angle_start,
angle_end,
radius,
})
};
let case2 = || {
let obj = arg.as_object()?;
let_field_of!(obj, to);
let_field_of!(obj, center);
Some(Self::CenterToRadius { center, to, radius })
};
case1().or_else(case2)
}
}
impl<'a> FromKclValue<'a> for super::sketch::ArcToData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, end);
let_field_of!(obj, interior);
Some(Self { end, interior })
}
}
impl<'a> FromKclValue<'a> for super::sketch::TangentialArcData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, radius);
let_field_of!(obj, offset);
Some(Self::RadiusAndOffset { radius, offset })
}
}
impl<'a> FromKclValue<'a> for crate::execution::Point3d {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
// Case 1: object with x/y/z fields
if let Some(obj) = arg.as_object() {
let_field_of!(obj, x);
let_field_of!(obj, y);
let_field_of!(obj, z);
return Some(Self { x, y, z });
}
// Case 2: Array of 3 numbers.
let [x, y, z]: [f64; 3] = FromKclValue::from_kcl_val(arg)?;
Some(Self { x, y, z })
}
}
impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
// Case 0: actual plane
if let KclValue::Plane { value } = arg {
return Some(Self::Plane {
origin: value.origin,
x_axis: value.x_axis,
y_axis: value.y_axis,
z_axis: value.z_axis,
});
}
// Case 1: predefined plane
if let Some(s) = arg.as_str() {
return match s {
"XY" | "xy" => Some(Self::XY),
"-XY" | "-xy" => Some(Self::NegXY),
"XZ" | "xz" => Some(Self::XZ),
"-XZ" | "-xz" => Some(Self::NegXZ),
"YZ" | "yz" => Some(Self::YZ),
"-YZ" | "-yz" => Some(Self::NegYZ),
_ => None,
};
}
// Case 2: custom plane
let obj = arg.as_object()?;
let_field_of!(obj, plane, &KclObjectFields);
let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val)?;
let x_axis = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?;
let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val)?;
let z_axis = plane.get("zAxis").and_then(FromKclValue::from_kcl_val)?;
Some(Self::Plane {
origin,
x_axis,
y_axis,
z_axis,
})
}
}
impl<'a> FromKclValue<'a> for crate::execution::ExtrudePlane {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, face_id "faceId");
let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
let_field_of!(obj, geo_meta "geoMeta");
Some(Self { face_id, tag, geo_meta })
}
}
impl<'a> FromKclValue<'a> for crate::execution::ExtrudeArc {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, face_id "faceId");
let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
let_field_of!(obj, geo_meta "geoMeta");
Some(Self { face_id, tag, geo_meta })
}
}
impl<'a> FromKclValue<'a> for crate::execution::GeoMeta {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, id);
let_field_of!(obj, source_range "sourceRange");
Some(Self {
id,
metadata: Metadata { source_range },
})
}
}
impl<'a> FromKclValue<'a> for crate::execution::ChamferSurface {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, face_id "faceId");
let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
let_field_of!(obj, geo_meta "geoMeta");
Some(Self { face_id, tag, geo_meta })
}
}
impl<'a> FromKclValue<'a> for crate::execution::FilletSurface {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, face_id "faceId");
let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
let_field_of!(obj, geo_meta "geoMeta");
Some(Self { face_id, tag, geo_meta })
}
}
impl<'a> FromKclValue<'a> for ExtrudeSurface {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let case1 = crate::execution::ExtrudePlane::from_kcl_val;
let case2 = crate::execution::ExtrudeArc::from_kcl_val;
let case3 = crate::execution::ChamferSurface::from_kcl_val;
let case4 = crate::execution::FilletSurface::from_kcl_val;
case1(arg)
.map(Self::ExtrudePlane)
.or_else(|| case2(arg).map(Self::ExtrudeArc))
.or_else(|| case3(arg).map(Self::Chamfer))
.or_else(|| case4(arg).map(Self::Fillet))
}
}
impl<'a> FromKclValue<'a> for crate::execution::EdgeCut {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, typ "type");
let tag = Box::new(obj.get("tag").and_then(FromKclValue::from_kcl_val));
let_field_of!(obj, edge_id "edgeId");
let_field_of!(obj, id);
match typ {
"fillet" => {
let_field_of!(obj, radius);
Some(Self::Fillet {
edge_id,
tag,
id,
radius,
})
}
"chamfer" => {
let_field_of!(obj, length);
Some(Self::Chamfer {
id,
length,
edge_id,
tag,
})
}
_ => None,
}
}
}
macro_rules! impl_from_kcl_for_vec {
($typ:path) => {
impl<'a> FromKclValue<'a> for Vec<$typ> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.as_array()?
.iter()
.map(|value| FromKclValue::from_kcl_val(value))
.collect::<Option<_>>()
}
}
};
}
impl_from_kcl_for_vec!(FaceTag);
impl_from_kcl_for_vec!(crate::execution::EdgeCut);
impl_from_kcl_for_vec!(crate::execution::Metadata);
impl_from_kcl_for_vec!(super::fillet::EdgeReference);
impl_from_kcl_for_vec!(ExtrudeSurface);
impl<'a> FromKclValue<'a> for SourceRange {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::MixedArray { value, meta: _ } = arg else {
return None;
};
if value.len() != 3 {
return None;
}
let v0 = value.first()?;
let v1 = value.get(1)?;
let v2 = value.get(2)?;
Some(SourceRange::new(
v0.as_usize()?,
v1.as_usize()?,
ModuleId::from_usize(v2.as_usize()?),
))
}
}
impl<'a> FromKclValue<'a> for crate::execution::Metadata {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
FromKclValue::from_kcl_val(arg).map(|sr| Self { source_range: sr })
}
}
impl<'a> FromKclValue<'a> for crate::execution::Solid {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.as_solid().cloned()
}
}
impl<'a> FromKclValue<'a> for crate::execution::SolidOrSketchOrImportedGeometry {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])),
KclValue::Sketch { value } => Some(Self::SketchSet(vec![(**value).clone()])),
KclValue::HomArray { value, .. } => {
let mut solids = vec![];
let mut sketches = vec![];
for item in value {
match item {
KclValue::Solid { value } => solids.push((**value).clone()),
KclValue::Sketch { value } => sketches.push((**value).clone()),
_ => return None,
}
}
if !solids.is_empty() {
Some(Self::SolidSet(solids))
} else {
Some(Self::SketchSet(sketches))
}
}
KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for super::sketch::SketchData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
// Order is critical since PlaneData is a subset of Plane.
let case1 = crate::execution::Plane::from_kcl_val;
let case2 = super::sketch::PlaneData::from_kcl_val;
let case3 = crate::execution::Solid::from_kcl_val;
let case4 = <Vec<Solid>>::from_kcl_val;
case1(arg)
.map(Box::new)
.map(Self::Plane)
.or_else(|| case2(arg).map(Self::PlaneOrientation))
.or_else(|| case3(arg).map(Box::new).map(Self::Solid))
.or_else(|| case4(arg).map(|v| Box::new(v[0].clone())).map(Self::Solid))
}
}
impl<'a> FromKclValue<'a> for super::axis_or_reference::AxisAndOrigin2d {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
// Case 1: predefined planes.
if let Some(s) = arg.as_str() {
return match s {
"X" | "x" => Some(Self::X),
"Y" | "y" => Some(Self::Y),
"-X" | "-x" => Some(Self::NegX),
"-Y" | "-y" => Some(Self::NegY),
_ => None,
};
}
// Case 2: custom planes.
let obj = arg.as_object()?;
let_field_of!(obj, custom, &KclObjectFields);
let_field_of!(custom, origin);
let_field_of!(custom, axis);
Some(Self::Custom { axis, origin })
}
}
impl<'a> FromKclValue<'a> for super::axis_or_reference::AxisAndOrigin3d {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
// Case 1: predefined planes.
if let Some(s) = arg.as_str() {
return match s {
"X" | "x" => Some(Self::X),
"Y" | "y" => Some(Self::Y),
"Z" | "z" => Some(Self::Z),
"-X" | "-x" => Some(Self::NegX),
"-Y" | "-y" => Some(Self::NegY),
"-Z" | "-z" => Some(Self::NegZ),
_ => None,
};
}
// Case 2: custom planes.
let obj = arg.as_object()?;
let_field_of!(obj, custom, &KclObjectFields);
let_field_of!(custom, origin);
let_field_of!(custom, axis);
Some(Self::Custom { axis, origin })
}
}
impl<'a> FromKclValue<'a> for super::fillet::EdgeReference {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let id = arg.as_uuid().map(Self::Uuid);
let tag = || TagIdentifier::from_kcl_val(arg).map(Box::new).map(Self::Tag);
id.or_else(tag)
}
}
impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis2dOrEdgeReference {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let case1 = super::axis_or_reference::AxisAndOrigin2d::from_kcl_val;
let case2 = super::fillet::EdgeReference::from_kcl_val;
case1(arg).map(Self::Axis).or_else(|| case2(arg).map(Self::Edge))
}
}
impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrEdgeReference {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let case1 = super::axis_or_reference::AxisAndOrigin3d::from_kcl_val;
let case2 = super::fillet::EdgeReference::from_kcl_val;
case1(arg).map(Self::Axis).or_else(|| case2(arg).map(Self::Edge))
}
}
impl<'a> FromKclValue<'a> for super::mirror::Mirror2dData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, axis);
Some(Self { axis })
}
}
impl<'a> FromKclValue<'a> for super::sketch::AngledLineData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let case1 = |arg: &KclValue| {
let obj = arg.as_object()?;
let_field_of!(obj, angle);
let_field_of!(obj, length);
Some(Self::AngleAndLengthNamed { angle, length })
};
let case2 = |arg: &KclValue| {
let array = arg.as_array()?;
let ang = array.first()?.as_f64()?;
let len = array.get(1)?.as_f64()?;
Some(Self::AngleAndLengthPair([ang, len]))
};
case1(arg).or_else(|| case2(arg))
}
}
impl<'a> FromKclValue<'a> for i64 {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for &'a str {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::String { value, meta: _ } = arg else {
return None;
};
Some(value)
}
}
impl<'a> FromKclValue<'a> for &'a KclObjectFields {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Object { value, meta: _ } = arg else {
return None;
};
Some(value)
}
}
impl<'a> FromKclValue<'a> for uuid::Uuid {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Uuid { value, meta: _ } = arg else {
return None;
};
Some(*value)
}
}
impl<'a> FromKclValue<'a> for u32 {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Number { value, .. } => crate::try_f64_to_u32(*value),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for NonZeroU32 {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
u32::from_kcl_val(arg).and_then(|x| x.try_into().ok())
}
}
impl<'a> FromKclValue<'a> for u64 {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Number { value, .. } => crate::try_f64_to_u64(*value),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for f64 {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Number { value, .. } => Some(*value),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for TyF64 {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, ty.clone())),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for Sketch {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Sketch { value } = arg else {
return None;
};
Some(value.as_ref().to_owned())
}
}
impl<'a> FromKclValue<'a> for Helix {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Helix { value } = arg else {
return None;
};
Some(value.as_ref().to_owned())
}
}
impl<'a> FromKclValue<'a> for SweepPath {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let case1 = Sketch::from_kcl_val;
let case2 = <Vec<Sketch>>::from_kcl_val;
let case3 = Helix::from_kcl_val;
case1(arg)
.map(Self::Sketch)
.or_else(|| case2(arg).map(|arg0: Vec<Sketch>| Self::Sketch(arg0[0].clone())))
.or_else(|| case3(arg).map(|arg0: Helix| Self::Helix(Box::new(arg0))))
}
}
impl<'a> FromKclValue<'a> for String {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::String { value, meta: _ } = arg else {
return None;
};
Some(value.to_owned())
}
}
impl<'a> FromKclValue<'a> for crate::parsing::ast::types::KclNone {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::KclNone { value, meta: _ } = arg else {
return None;
};
Some(value.to_owned())
}
}
impl<'a> FromKclValue<'a> for bool {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Bool { value, meta: _ } = arg else {
return None;
};
Some(*value)
}
}
impl<'a> FromKclValue<'a> for Box<Solid> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Solid { value } = arg else {
return None;
};
Some(value.to_owned())
}
}
impl<'a> FromKclValue<'a> for Vec<Solid> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::HomArray { value, .. } = arg else {
return None;
};
value.iter().map(Solid::from_kcl_val).collect()
}
}
impl<'a> FromKclValue<'a> for Vec<Sketch> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::HomArray { value, .. } = arg else {
return None;
};
value.iter().map(Sketch::from_kcl_val).collect()
}
}
impl<'a> FromKclValue<'a> for &'a FunctionSource {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.get_function()
}
}
impl<'a> FromKclValue<'a> for SketchOrSurface {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Sketch { value: sg } => Some(Self::Sketch(sg.to_owned())),
KclValue::Plane { value } => Some(Self::SketchSurface(SketchSurface::Plane(value.clone()))),
KclValue::Face { value } => Some(Self::SketchSurface(SketchSurface::Face(value.clone()))),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for SketchSurface {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Plane { value } => Some(Self::Plane(value.clone())),
KclValue::Face { value } => Some(Self::Face(value.clone())),
_ => None,
}
}
}
impl From<Args> for Metadata {
fn from(value: Args) -> Self {
Self {
source_range: value.source_range,
}
}
}
impl From<Args> for Vec<Metadata> {
fn from(value: Args) -> Self {
vec![Metadata {
source_range: value.source_range,
}]
}
}