Use arrays for multiple geometry (#5770)

* Parse [T] instead of T[] for array types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* homogenous arrays, type coercion, remove solid set and sketch set, etc

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-03-17 17:57:26 +13:00
committed by GitHub
parent 75a975b1e1
commit a8b0e1a771
97 changed files with 325236 additions and 323291 deletions

View File

@ -12,9 +12,9 @@ use serde::{Deserialize, Serialize};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
kcl_value::{FunctionSource, NumericType},
ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Sketch, SketchSet,
SketchSurface, Solid, SolidSet, TagIdentifier,
kcl_value::{ArrayLen, FunctionSource, NumericType, RuntimeType},
ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PrimitiveType, Sketch,
SketchSurface, Solid, TagIdentifier,
},
parsing::ast::types::TagNode,
source_range::SourceRange,
@ -233,9 +233,41 @@ impl Args {
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 msg_base = format!("This function expected the input argument to be of type {expected_type_name} but it's actually of type {actual_type_name}");
let suggestion = match (expected_type_name.as_str(), actual_type_name) {
("SolidSet", "Sketch") => Some(
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")
| (RuntimeType::Array(PrimitiveType::Solid, _), "Sketch") => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
),
_ => None,
@ -248,7 +280,10 @@ impl Args {
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.
@ -338,10 +373,10 @@ impl Args {
/// Flush just the fillets and chamfers for this specific SolidSet.
#[allow(clippy::vec_box)]
pub(crate) async fn flush_batch_for_solid_set(
pub(crate) async fn flush_batch_for_solids(
&self,
exec_state: &mut ExecState,
solids: Vec<Box<Solid>>,
solids: Vec<Solid>,
) -> Result<(), KclError> {
// Make sure we don't traverse sketches more than once.
let mut traversed_sketches = Vec::new();
@ -510,12 +545,48 @@ impl Args {
Ok((a.n, b.n, ty))
}
pub(crate) fn get_sketches(&self) -> Result<(SketchSet, Sketch), KclError> {
FromArgs::from_args(self, 0)
pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> {
let sarg = self.args[0]
.value
.coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!(
"Expected an array of sketches, found {}",
self.args[0].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 sarg = self.args[1]
.value
.coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!("Expected a sketch, found {}", self.args[1].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) -> Result<Sketch, KclError> {
FromArgs::from_args(self, 0)
pub(crate) fn get_sketch(&self, exec_state: &mut ExecState) -> Result<Sketch, KclError> {
let sarg = self.args[0]
.value
.coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!("Expected a sketch, found {}", self.args[0].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>
@ -536,18 +607,55 @@ impl Args {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_sketch_set<'a, T>(&'a self) -> Result<(T, SketchSet), KclError>
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>,
{
FromArgs::from_args(self, 0)
let data: T = FromArgs::from_args(self, 0)?;
let sarg = self.args[1]
.value
.coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!(
"Expected an array of sketches for second argument, found {}",
self.args[1].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) -> Result<(T, Sketch, Option<TagNode>), KclError>
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,
{
FromArgs::from_args(self, 0)
let data: T = FromArgs::from_args(self, 0)?;
let sarg = self.args[1]
.value
.coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!(
"Expected a sketch for second argument, found {}",
self.args[1].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>
@ -557,11 +665,26 @@ impl Args {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_solid<'a, T>(&'a self) -> Result<(T, Box<Solid>), KclError>
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,
{
FromArgs::from_args(self, 0)
let data: T = FromArgs::from_args(self, 0)?;
let sarg = self.args[1]
.value
.coerce(&RuntimeType::Primitive(PrimitiveType::Solid), exec_state)
.ok_or(KclError::Type(KclErrorDetails {
message: format!(
"Expected a solid for second argument, found {}",
self.args[1].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> {
@ -1304,7 +1427,6 @@ 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_from_kcl_for_vec!(Sketch);
impl<'a> FromKclValue<'a> for SourceRange {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
@ -1340,8 +1462,10 @@ impl<'a> FromKclValue<'a> for crate::execution::Solid {
impl<'a> FromKclValue<'a> for crate::execution::SolidOrImportedGeometry {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Solid { value } => Some(Self::Solid(value.clone())),
KclValue::Solids { value } => Some(Self::SolidSet(value.clone())),
KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])),
KclValue::HomArray { value, .. } => Some(Self::SolidSet(
value.iter().map(|v| v.as_solid().unwrap().clone()).collect(),
)),
KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
_ => None,
}
@ -1354,11 +1478,13 @@ impl<'a> FromKclValue<'a> for super::sketch::SketchData {
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))
}
}
@ -1531,6 +1657,7 @@ impl<'a> FromKclValue<'a> for TyF64 {
}
}
}
impl<'a> FromKclValue<'a> for Sketch {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Sketch { value } = arg else {
@ -1548,13 +1675,16 @@ impl<'a> FromKclValue<'a> for Helix {
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 = Helix::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: Helix| Self::Helix(Box::new(arg0))))
.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 {
@ -1582,20 +1712,6 @@ impl<'a> FromKclValue<'a> for bool {
}
}
impl<'a> FromKclValue<'a> for SketchSet {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Sketch { value: sketch } => Some(SketchSet::from(sketch.to_owned())),
KclValue::Sketches { value } => Some(SketchSet::from(value.to_owned())),
KclValue::MixedArray { .. } => {
let v: Option<Vec<Sketch>> = FromKclValue::from_kcl_val(arg);
Some(SketchSet::Sketches(v?.iter().cloned().map(Box::new).collect()))
}
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for Box<Solid> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Solid { value } = arg else {
@ -1605,15 +1721,27 @@ impl<'a> FromKclValue<'a> for Box<Solid> {
}
}
impl<'a> FromKclValue<'a> for &'a FunctionSource {
impl<'a> FromKclValue<'a> for Vec<Solid> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.get_function()
let KclValue::HomArray { value, .. } = arg else {
return None;
};
value.iter().map(Solid::from_kcl_val).collect()
}
}
impl<'a> FromKclValue<'a> for SolidSet {
impl<'a> FromKclValue<'a> for Vec<Sketch> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.get_solid_set().ok()
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()
}
}