Compute each repetition's transform.
This commit is contained in:
@ -1135,7 +1135,7 @@ impl CallExpression {
|
|||||||
match ctx.stdlib.get_either(&self.callee.name) {
|
match ctx.stdlib.get_either(&self.callee.name) {
|
||||||
FunctionKind::Core(func) => {
|
FunctionKind::Core(func) => {
|
||||||
// Attempt to call the function.
|
// Attempt to call the function.
|
||||||
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone(), memory.clone());
|
||||||
let result = func.std_lib_fn()(args).await?;
|
let result = func.std_lib_fn()(args).await?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1326,7 +1326,7 @@ impl ExecutorContext {
|
|||||||
}
|
}
|
||||||
match self.stdlib.get_either(&call_expr.callee.name) {
|
match self.stdlib.get_either(&call_expr.callee.name) {
|
||||||
FunctionKind::Core(func) => {
|
FunctionKind::Core(func) => {
|
||||||
let args = crate::std::Args::new(args, call_expr.into(), self.clone());
|
let args = crate::std::Args::new(args, call_expr.into(), self.clone(), memory.clone());
|
||||||
let result = func.std_lib_fn()(args).await?;
|
let result = func.std_lib_fn()(args).await?;
|
||||||
memory.return_ = Some(ProgramReturn::Value(result));
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,8 +32,8 @@ use crate::{
|
|||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{
|
executor::{
|
||||||
ExecutorContext, ExtrudeGroup, ExtrudeGroupSet, MemoryFunction, MemoryItem, Metadata, SketchGroup,
|
ExecutorContext, ExtrudeGroup, ExtrudeGroupSet, MemoryFunction, MemoryItem, Metadata, ProgramMemory,
|
||||||
SketchGroupSet, SketchSurface, SourceRange,
|
SketchGroup, SketchGroupSet, SketchSurface, SourceRange,
|
||||||
},
|
},
|
||||||
std::{kcl_stdlib::KclStdLibFn, sketch::SketchOnFaceTag},
|
std::{kcl_stdlib::KclStdLibFn, sketch::SketchOnFaceTag},
|
||||||
};
|
};
|
||||||
@ -206,14 +206,17 @@ pub struct Args {
|
|||||||
pub args: Vec<MemoryItem>,
|
pub args: Vec<MemoryItem>,
|
||||||
pub source_range: SourceRange,
|
pub source_range: SourceRange,
|
||||||
pub ctx: ExecutorContext,
|
pub ctx: ExecutorContext,
|
||||||
|
// TODO: This should be reference, not clone.
|
||||||
|
pub memory: ProgramMemory,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
pub fn new(args: Vec<MemoryItem>, source_range: SourceRange, ctx: ExecutorContext) -> Self {
|
pub fn new(args: Vec<MemoryItem>, source_range: SourceRange, ctx: ExecutorContext, memory: ProgramMemory) -> Self {
|
||||||
Self {
|
Self {
|
||||||
args,
|
args,
|
||||||
source_range,
|
source_range,
|
||||||
ctx,
|
ctx,
|
||||||
|
memory,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,9 +393,7 @@ impl Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Works with either 2D or 3D solids.
|
/// Works with either 2D or 3D solids.
|
||||||
fn get_pattern_args(
|
fn get_pattern_args(&self) -> std::result::Result<(u32, FnAsArg<'_>, Vec<Uuid>), KclError> {
|
||||||
&self,
|
|
||||||
) -> std::result::Result<(u32, (&MemoryFunction, Box<FunctionExpression>), Vec<Uuid>), KclError> {
|
|
||||||
let sr = vec![self.source_range];
|
let sr = vec![self.source_range];
|
||||||
let mut args = self.args.iter();
|
let mut args = self.args.iter();
|
||||||
let num_repetitions = args.next().ok_or_else(|| {
|
let num_repetitions = args.next().ok_or_else(|| {
|
||||||
@ -408,7 +409,7 @@ impl Args {
|
|||||||
source_ranges: sr.clone(),
|
source_ranges: sr.clone(),
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
let (transform, expression) = transform.get_function(sr.clone())?;
|
let (transform, expr) = transform.get_function(sr.clone())?;
|
||||||
let sg = args.next().ok_or_else(|| {
|
let sg = args.next().ok_or_else(|| {
|
||||||
KclError::Type(KclErrorDetails {
|
KclError::Type(KclErrorDetails {
|
||||||
message: "Missing third argument (should be a Sketch/ExtrudeGroup or an array of Sketch/ExtrudeGroups)"
|
message: "Missing third argument (should be a Sketch/ExtrudeGroup or an array of Sketch/ExtrudeGroups)"
|
||||||
@ -423,7 +424,7 @@ impl Args {
|
|||||||
(_, Ok(group)) => group.ids(),
|
(_, Ok(group)) => group.ids(),
|
||||||
(Err(e), _) => return Err(e),
|
(Err(e), _) => return Err(e),
|
||||||
};
|
};
|
||||||
Ok((num_repetitions, (transform, expression), entity_ids))
|
Ok((num_repetitions, FnAsArg { func: transform, expr }, entity_ids))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_segment_name_sketch_group(&self) -> Result<(String, Box<SketchGroup>), KclError> {
|
fn get_segment_name_sketch_group(&self) -> Result<(String, Box<SketchGroup>), KclError> {
|
||||||
@ -1049,6 +1050,11 @@ pub enum Primitive {
|
|||||||
Uuid,
|
Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FnAsArg<'a> {
|
||||||
|
pub func: &'a MemoryFunction,
|
||||||
|
pub expr: Box<FunctionExpression>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
|||||||
@ -10,8 +10,8 @@ use uuid::Uuid;
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{
|
executor::{
|
||||||
ExtrudeGroup, ExtrudeGroupSet, FunctionParam, Geometries, Geometry, MemoryItem, Point3d, SketchGroup,
|
ExtrudeGroup, ExtrudeGroupSet, FunctionParam, Geometries, Geometry, MemoryItem, Point3d, ProgramReturn,
|
||||||
SketchGroupSet, UserVal,
|
SketchGroup, SketchGroupSet, SourceRange, UserVal,
|
||||||
},
|
},
|
||||||
std::{types::Uint, Args},
|
std::{types::Uint, Args},
|
||||||
};
|
};
|
||||||
@ -19,23 +19,23 @@ use crate::{
|
|||||||
const CANNOT_USE_ZERO_VECTOR: &str =
|
const CANNOT_USE_ZERO_VECTOR: &str =
|
||||||
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place.";
|
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place.";
|
||||||
|
|
||||||
/// How to change each element of a pattern.
|
// /// How to change each element of a pattern.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
// #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
// #[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
// #[serde(rename_all = "camelCase")]
|
||||||
pub struct LinearTransform {
|
// pub struct LinearTransform {
|
||||||
/// Translate the replica this far along each dimension.
|
// /// Translate the replica this far along each dimension.
|
||||||
/// Defaults to zero vector (i.e. same position as the original).
|
// /// Defaults to zero vector (i.e. same position as the original).
|
||||||
#[serde(default)]
|
// #[serde(default)]
|
||||||
pub translate: Option<Point3d>,
|
// pub translate: Option<Point3d>,
|
||||||
/// Scale the replica's size along each axis.
|
// /// Scale the replica's size along each axis.
|
||||||
/// Defaults to (1, 1, 1) (i.e. the same size as the original).
|
// /// Defaults to (1, 1, 1) (i.e. the same size as the original).
|
||||||
#[serde(default)]
|
// #[serde(default)]
|
||||||
pub scale: Option<Point3d>,
|
// pub scale: Option<Point3d>,
|
||||||
/// Whether to replicate the original solid in this instance.
|
// /// Whether to replicate the original solid in this instance.
|
||||||
#[serde(default)]
|
// #[serde(default)]
|
||||||
pub replicate: Option<bool>,
|
// pub replicate: Option<bool>,
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Data for a linear pattern on a 2D sketch.
|
/// Data for a linear pattern on a 2D sketch.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
@ -99,16 +99,16 @@ impl LinearPattern {
|
|||||||
/// Each element in the pattern repeats a particular piece of geometry.
|
/// Each element in the pattern repeats a particular piece of geometry.
|
||||||
/// The repetitions can be transformed by the `transform` parameter.
|
/// The repetitions can be transformed by the `transform` parameter.
|
||||||
pub async fn pattern(args: Args) -> Result<MemoryItem, KclError> {
|
pub async fn pattern(args: Args) -> Result<MemoryItem, KclError> {
|
||||||
let (num_repetitions, (transform_fn, transform_expr), entity_ids) = args.get_pattern_args()?;
|
let (num_repetitions, transform, entity_ids) = args.get_pattern_args()?;
|
||||||
|
|
||||||
let sketch_groups = inner_pattern(
|
let sketch_groups = inner_pattern(
|
||||||
num_repetitions,
|
num_repetitions,
|
||||||
FunctionParam {
|
FunctionParam {
|
||||||
inner: transform_fn,
|
inner: transform.func,
|
||||||
fn_expr: transform_expr,
|
fn_expr: transform.expr,
|
||||||
meta: vec![args.source_range.into()],
|
meta: vec![args.source_range.into()],
|
||||||
ctx: args.ctx,
|
ctx: args.ctx.clone(),
|
||||||
memory: todo!(),
|
memory: args.memory.clone(),
|
||||||
},
|
},
|
||||||
entity_ids,
|
entity_ids,
|
||||||
&args,
|
&args,
|
||||||
@ -170,18 +170,64 @@ async fn inner_pattern<'a>(
|
|||||||
// Build the vec of transforms, one for each repetition.
|
// Build the vec of transforms, one for each repetition.
|
||||||
let mut transforms = Vec::new();
|
let mut transforms = Vec::new();
|
||||||
for i in 0..num_repetitions {
|
for i in 0..num_repetitions {
|
||||||
|
// Call the transform fn for this repetition.
|
||||||
let repetition_num = MemoryItem::UserVal(UserVal {
|
let repetition_num = MemoryItem::UserVal(UserVal {
|
||||||
value: serde_json::Value::Number(i.into()),
|
value: serde_json::Value::Number(i.into()),
|
||||||
meta: vec![args.source_range.into()],
|
meta: vec![args.source_range.into()],
|
||||||
});
|
});
|
||||||
// The user-defined `transform` function takes 1 argument, the index number
|
let transform_fn_args = vec![repetition_num];
|
||||||
// of which repetition the transform is for.
|
let transform_fn_return = transform_function.call(transform_fn_args).await?;
|
||||||
let args = vec![repetition_num];
|
|
||||||
let xform = transform_function.call(args);
|
// Unpack the returned transform object.
|
||||||
transforms.push(xform);
|
let transform_fn_return = transform_fn_return.ok_or_else(|| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Transform function must return a value".to_string(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
let ProgramReturn::Value(transform_fn_return) = transform_fn_return else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Transform function must return a value".to_string(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let MemoryItem::UserVal(transform) = transform_fn_return else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Transform function must return a transform object".to_string(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply defaults to the transform.
|
||||||
|
let replicate = match transform.value.get("replicate") {
|
||||||
|
Some(serde_json::Value::Bool(true)) => true,
|
||||||
|
Some(serde_json::Value::Bool(false)) => false,
|
||||||
|
Some(_) => {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "The 'replicate' key must be a bool".to_string(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
let scale = match transform.value.get("scale") {
|
||||||
|
Some(x) => array_to_point3d(x, vec![args.source_range])?,
|
||||||
|
None => Point3d { x: 1.0, y: 1.0, z: 1.0 },
|
||||||
|
};
|
||||||
|
let translate = match transform.value.get("translate") {
|
||||||
|
Some(x) => array_to_point3d(x, vec![args.source_range])?,
|
||||||
|
None => Point3d { x: 0.0, y: 0.0, z: 0.0 },
|
||||||
|
};
|
||||||
|
let t = kittycad::types::LinearTransform {
|
||||||
|
replicate,
|
||||||
|
scale: Some(scale.into()),
|
||||||
|
translate: Some(translate.into()),
|
||||||
|
};
|
||||||
|
transforms.push(dbg!(t));
|
||||||
}
|
}
|
||||||
for id in ids {
|
for id in ids {
|
||||||
// Call the pattern API endpoint.
|
// Call the pattern API endpoint.
|
||||||
|
send_pattern_cmd(id, transforms.clone(), args).await?;
|
||||||
}
|
}
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
@ -311,6 +357,27 @@ async fn inner_pattern_linear_3d(
|
|||||||
Ok(extrude_groups)
|
Ok(extrude_groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn send_pattern_cmd(
|
||||||
|
entity_id: Uuid,
|
||||||
|
transform: Vec<kittycad::types::LinearTransform>,
|
||||||
|
args: &Args,
|
||||||
|
) -> Result<kittycad::types::EntityLinearPatternTransform, KclError> {
|
||||||
|
let id = uuid::Uuid::new_v4();
|
||||||
|
let resp = args
|
||||||
|
.send_modeling_cmd(id, ModelingCmd::EntityLinearPatternTransform { entity_id, transform })
|
||||||
|
.await?;
|
||||||
|
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
|
modeling_response: kittycad::types::OkModelingCmdResponse::EntityLinearPatternTransform { data: pattern_info },
|
||||||
|
} = &resp
|
||||||
|
else {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("EntityLinearPatternTransform response was not as expected: {:?}", resp),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
Ok(pattern_info.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
async fn pattern_linear(data: LinearPattern, geometry: Geometry, args: Args) -> Result<Geometries, KclError> {
|
async fn pattern_linear(data: LinearPattern, geometry: Geometry, args: Args) -> Result<Geometries, KclError> {
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
@ -623,3 +690,42 @@ async fn pattern_circular(data: CircularPattern, geometry: Geometry, args: Args)
|
|||||||
|
|
||||||
Ok(geometries)
|
Ok(geometries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn array_to_point3d(json: &serde_json::Value, source_ranges: Vec<SourceRange>) -> Result<Point3d, KclError> {
|
||||||
|
let serde_json::Value::Array(arr) = dbg!(json) else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Expected an array of 3 numbers (i.e. a 3D point)".to_string(),
|
||||||
|
source_ranges,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let len = arr.len();
|
||||||
|
if len != 3 {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Expected an array of 3 numbers (i.e. a 3D point) but found {len} items"),
|
||||||
|
source_ranges,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let Some(x) = arr[0].as_number().and_then(|num| num.as_f64()) else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "X component of this point was not a number".to_owned(),
|
||||||
|
source_ranges,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let Some(y) = arr[1].as_number().and_then(|num| num.as_f64()) else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Y component of this point was not a number".to_owned(),
|
||||||
|
source_ranges,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let Some(z) = arr[2].as_number().and_then(|num| num.as_f64()) else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Z component of this point was not a number".to_owned(),
|
||||||
|
source_ranges,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
Ok(Point3d {
|
||||||
|
x: x.to_owned().into(),
|
||||||
|
y: y.to_owned(),
|
||||||
|
z: z.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -9,9 +9,10 @@ const t = 0.005 // taper factor [0-1)
|
|||||||
// Defines how to modify each layer of the vase.
|
// Defines how to modify each layer of the vase.
|
||||||
// Each replica is shifted up the Z axis, and has a smoothly-varying radius
|
// Each replica is shifted up the Z axis, and has a smoothly-varying radius
|
||||||
fn transform = (replicaId) => {
|
fn transform = (replicaId) => {
|
||||||
|
let scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
|
||||||
return {
|
return {
|
||||||
translate: [0, 0, replicaId * 10],
|
translate: [0, 0, replicaId * 10],
|
||||||
scale: r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
|
scale: [scale, scale, 0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,10 +21,10 @@ fn layer = () => {
|
|||||||
return startSketchOn("XY") // or some other plane idk
|
return startSketchOn("XY") // or some other plane idk
|
||||||
|> circle([0, 0], 1, %, 'tag1')
|
|> circle([0, 0], 1, %, 'tag1')
|
||||||
|> extrude(h, %)
|
|> extrude(h, %)
|
||||||
|> fillet({
|
// |> fillet({
|
||||||
radius: h / 2.01,
|
// radius: h / 2.01,
|
||||||
tags: ["tag1", getOppositeEdge("tag1", %)]
|
// tags: ["tag1", getOppositeEdge("tag1", %)]
|
||||||
}, %)
|
// }, %)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The vase is 100 layers tall.
|
// The vase is 100 layers tall.
|
||||||
|
|||||||
BIN
src/wasm-lib/tests/executor/outputs/pattern_vase.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/pattern_vase.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 333 KiB |
Reference in New Issue
Block a user