From cede44aacf945d2d379db1944fa414f9d5a94ab3 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Wed, 13 Mar 2024 22:15:26 -0500 Subject: [PATCH] Grackle: Extrude/ClosePath stdlib functions (#1717) * Grackle: Extrude stdlib function * Grackle: ClosePath function --- src/wasm-lib/Cargo.lock | 12 +- src/wasm-lib/grackle/src/binding_scope.rs | 8 ++ src/wasm-lib/grackle/src/lib.rs | 6 +- .../grackle/src/native_functions/sketch.rs | 2 +- .../sketch/stdlib_functions.rs | 120 +++++++++++++++++- src/wasm-lib/grackle/src/tests.rs | 48 +++---- 6 files changed, 165 insertions(+), 31 deletions(-) diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index a7a3a2bf4..15c47a222 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -2007,7 +2007,7 @@ dependencies = [ [[package]] name = "kittycad-execution-plan" version = "0.1.0" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#adfae6ef5f658d447133016f284ac100c88893ed" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" dependencies = [ "bytes", "insta", @@ -2027,7 +2027,7 @@ dependencies = [ [[package]] name = "kittycad-execution-plan-macros" version = "0.1.9" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#adfae6ef5f658d447133016f284ac100c88893ed" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" dependencies = [ "proc-macro2", "quote", @@ -2037,7 +2037,7 @@ dependencies = [ [[package]] name = "kittycad-execution-plan-traits" version = "0.1.13" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#adfae6ef5f658d447133016f284ac100c88893ed" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" dependencies = [ "serde", "thiserror", @@ -2047,7 +2047,7 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds" version = "0.1.30" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#adfae6ef5f658d447133016f284ac100c88893ed" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" dependencies = [ "anyhow", "chrono", @@ -2075,7 +2075,7 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds-macros" version = "0.1.2" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#adfae6ef5f658d447133016f284ac100c88893ed" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" dependencies = [ "proc-macro2", "quote", @@ -2085,7 +2085,7 @@ dependencies = [ [[package]] name = "kittycad-modeling-session" version = "0.1.1" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#adfae6ef5f658d447133016f284ac100c88893ed" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" dependencies = [ "futures", "kittycad", diff --git a/src/wasm-lib/grackle/src/binding_scope.rs b/src/wasm-lib/grackle/src/binding_scope.rs index 9b461938f..b383d538a 100644 --- a/src/wasm-lib/grackle/src/binding_scope.rs +++ b/src/wasm-lib/grackle/src/binding_scope.rs @@ -113,6 +113,14 @@ impl BindingScope { "lineTo".into(), EpBinding::from(KclFunction::LineTo(native_functions::sketch::LineTo)), ), + ( + "extrude".into(), + EpBinding::from(KclFunction::Extrude(native_functions::sketch::Extrude)), + ), + ( + "close".into(), + EpBinding::from(KclFunction::Close(native_functions::sketch::Close)), + ), ]), parent: None, } diff --git a/src/wasm-lib/grackle/src/lib.rs b/src/wasm-lib/grackle/src/lib.rs index 177b7251c..40beaa511 100644 --- a/src/wasm-lib/grackle/src/lib.rs +++ b/src/wasm-lib/grackle/src/lib.rs @@ -24,7 +24,7 @@ use self::{ }; /// Execute a KCL program by compiling into an execution plan, then running that. -pub async fn execute(ast: Program, session: Option) -> Result { +pub async fn execute(ast: Program, session: &mut Option) -> Result { let mut planner = Planner::new(); let (plan, _retval) = planner.build_plan(ast)?; let mut mem = ep::Memory::default(); @@ -260,8 +260,10 @@ impl Planner { } = match callee { KclFunction::Id(f) => f.call(&mut ctx, args)?, KclFunction::StartSketchAt(f) => f.call(&mut ctx, args)?, + KclFunction::Extrude(f) => f.call(&mut ctx, args)?, KclFunction::LineTo(f) => f.call(&mut ctx, args)?, KclFunction::Add(f) => f.call(&mut ctx, args)?, + KclFunction::Close(f) => f.call(&mut ctx, args)?, KclFunction::UserDefined(f) => { let UserDefinedFunction { params_optional, @@ -631,6 +633,8 @@ enum KclFunction { LineTo(native_functions::sketch::LineTo), Add(native_functions::Add), UserDefined(UserDefinedFunction), + Extrude(native_functions::sketch::Extrude), + Close(native_functions::sketch::Close), } /// Context used when compiling KCL. diff --git a/src/wasm-lib/grackle/src/native_functions/sketch.rs b/src/wasm-lib/grackle/src/native_functions/sketch.rs index a7c6ee37a..6394d6df0 100644 --- a/src/wasm-lib/grackle/src/native_functions/sketch.rs +++ b/src/wasm-lib/grackle/src/native_functions/sketch.rs @@ -3,4 +3,4 @@ pub mod helpers; pub mod stdlib_functions; -pub use stdlib_functions::{LineTo, StartSketchAt}; +pub use stdlib_functions::{Close, Extrude, LineTo, StartSketchAt}; diff --git a/src/wasm-lib/grackle/src/native_functions/sketch/stdlib_functions.rs b/src/wasm-lib/grackle/src/native_functions/sketch/stdlib_functions.rs index c092091a5..d9f6dd18e 100644 --- a/src/wasm-lib/grackle/src/native_functions/sketch/stdlib_functions.rs +++ b/src/wasm-lib/grackle/src/native_functions/sketch/stdlib_functions.rs @@ -3,7 +3,7 @@ use kittycad_execution_plan::{ sketch_types::{self, Axes, BasePath, Plane, SketchGroup}, Destination, Instruction, }; -use kittycad_execution_plan_traits::{InMemory, Primitive, Value}; +use kittycad_execution_plan_traits::{Address, InMemory, Primitive, Value}; use kittycad_modeling_cmds::{ shared::{Point3d, Point4d}, ModelingCmdEndpoint, @@ -13,6 +13,124 @@ use uuid::Uuid; use super::helpers::{arg_point2d, no_arg_api_call, sg_binding, single_binding, stack_api_call}; use crate::{binding_scope::EpBinding, error::CompileError, native_functions::Callable, EvalPlan}; +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct Close; + +impl Callable for Close { + fn call( + &self, + _ctx: &mut crate::native_functions::Context<'_>, + args: Vec, + ) -> Result { + let mut instructions = Vec::new(); + let fn_name = "close"; + // Get all required params. + let mut args_iter = args.into_iter(); + let Some(sketch_group) = args_iter.next() else { + return Err(CompileError::NotEnoughArgs { + fn_name: fn_name.into(), + required: 1, + actual: 1, + }); + }; + // Check param type. + let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?; + let cmd_id = Uuid::new_v4().into(); + instructions.extend([ + // Push the path ID onto the stack. + Instruction::SketchGroupCopyFrom { + destination: Destination::StackPush, + length: 1, + source: sg, + offset: SketchGroup::path_id_offset(), + }, + // Call the 'extrude' API request. + Instruction::ApiRequest(ApiRequest { + endpoint: ModelingCmdEndpoint::ClosePath, + store_response: None, + arguments: vec![ + // Target (path ID) + InMemory::StackPop, + ], + cmd_id, + }), + ]); + + Ok(EvalPlan { + instructions, + binding: EpBinding::SketchGroup { index: sg }, + }) + } +} +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct Extrude; + +impl Callable for Extrude { + fn call( + &self, + _ctx: &mut crate::native_functions::Context<'_>, + args: Vec, + ) -> Result { + let mut instructions = Vec::new(); + let fn_name = "extrude"; + // Get all required params. + let mut args_iter = args.into_iter(); + let Some(height) = args_iter.next() else { + return Err(CompileError::NotEnoughArgs { + fn_name: fn_name.into(), + required: 2, + actual: 0, + }); + }; + let Some(sketch_group) = args_iter.next() else { + return Err(CompileError::NotEnoughArgs { + fn_name: fn_name.into(), + required: 2, + actual: 1, + }); + }; + // Check param type. + let height = single_binding(height, fn_name, "numeric height", 0)?; + let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?; + let cmd_id = Uuid::new_v4().into(); + instructions.extend([ + // Push the `cap` bool onto the stack. + Instruction::StackPush { + data: vec![true.into()], + }, + // Push the path ID onto the stack. + Instruction::SketchGroupCopyFrom { + destination: Destination::StackPush, + length: 1, + source: sg, + offset: SketchGroup::path_id_offset(), + }, + // Call the 'extrude' API request. + Instruction::ApiRequest(ApiRequest { + endpoint: ModelingCmdEndpoint::Extrude, + store_response: None, + arguments: vec![ + // Target + InMemory::StackPop, + // Height + InMemory::Address(height), + // Cap + InMemory::StackPop, + ], + cmd_id, + }), + ]); + + // TODO: make an ExtrudeGroup and store it. + Ok(EvalPlan { + instructions, + binding: EpBinding::Single(Address::ZERO + 999), + }) + } +} + #[derive(Debug, Clone)] #[cfg_attr(test, derive(Eq, PartialEq))] pub struct LineTo; diff --git a/src/wasm-lib/grackle/src/tests.rs b/src/wasm-lib/grackle/src/tests.rs index 5adc4738d..3902cd9d8 100644 --- a/src/wasm-lib/grackle/src/tests.rs +++ b/src/wasm-lib/grackle/src/tests.rs @@ -305,7 +305,7 @@ async fn computed_object_property() { let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) .ast() .unwrap(); - let mem = crate::execute(ast, None).await.unwrap(); + let mem = crate::execute(ast, &mut None).await.unwrap(); use ept::ReadMemory; // Should be 'true', based on the above KCL program. assert_eq!(mem.get(address_of_val).unwrap(), &ept::Primitive::Bool(true)); @@ -327,7 +327,7 @@ async fn computed_array_in_object() { let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) .ast() .unwrap(); - let mem = crate::execute(ast, None).await.unwrap(); + let mem = crate::execute(ast, &mut None).await.unwrap(); use ept::ReadMemory; // Should be 'true', based on the above KCL program. assert_eq!(mem.get(address_of_val).unwrap(), &ept::Primitive::Bool(true)); @@ -349,7 +349,7 @@ async fn computed_object_in_array() { let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) .ast() .unwrap(); - let mem = crate::execute(ast, None).await.unwrap(); + let mem = crate::execute(ast, &mut None).await.unwrap(); use ept::ReadMemory; // Should be 'true', based on the above KCL program. assert_eq!(mem.get(address_of_val).unwrap(), &ept::Primitive::Bool(true)); @@ -370,7 +370,7 @@ async fn computed_nested_object_property() { let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) .ast() .unwrap(); - let mem = crate::execute(ast, None).await.unwrap(); + let mem = crate::execute(ast, &mut None).await.unwrap(); use ept::ReadMemory; // Should be 'true', based on the above KCL program. assert_eq!(mem.get(address_of_val).unwrap(), &ept::Primitive::Bool(true)); @@ -465,7 +465,7 @@ async fn computed_array_index() { let tokens = kcl_lib::token::lexer(program); let parser = kcl_lib::parser::Parser::new(tokens); let ast = parser.ast().unwrap(); - let mem = crate::execute(ast, None).await.unwrap(); + let mem = crate::execute(ast, &mut None).await.unwrap(); use ept::ReadMemory; // Should be "b", as pointed out in the KCL program's comment. assert_eq!( @@ -1061,19 +1061,21 @@ fn kcvm_dbg(kcl_program: &str) { #[tokio::test] async fn stdlib_cube_partial() { let program = r#" - let cube = startSketchAt([1.0, 1.0], "adam") - |> lineTo([21.0 , 1.0], %, "side0") - |> lineTo([21.0 , 21.0], %, "side1") - |> lineTo([ 1.0 , 21.0], %, "side2") - |> lineTo([ 1.0 , 1.0], %, "side3") + let cube = startSketchAt([10.0, 10.0], "adam") + |> lineTo([210.0 , 10.0], %, "side0") + |> lineTo([210.0 , 210.0], %, "side1") + |> lineTo([ 10.0 , 210.0], %, "side2") + |> lineTo([ 10.0 , 10.0], %, "side3") + |> close(%) + |> extrude(100.0, %) "#; let (_plan, _scope, last_address) = must_plan(program); - assert_eq!(last_address, Address::ZERO + 65); + assert_eq!(last_address, Address::ZERO + 66); let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) .ast() .unwrap(); - let client = test_client().await; - let mem = match crate::execute(ast, Some(client)).await { + let mut client = Some(test_client().await); + let mem = match crate::execute(ast, &mut client).await { Ok(mem) => mem, Err(e) => panic!("{e}"), }; @@ -1083,36 +1085,37 @@ async fn stdlib_cube_partial() { vec![ sketch_types::PathSegment::ToPoint { base: sketch_types::BasePath { - from: Point2d { x: 1.0, y: 1.0 }, - to: Point2d { x: 21.0, y: 1.0 }, + from: Point2d { x: 10.0, y: 10.0 }, + to: Point2d { x: 210.0, y: 10.0 }, name: "side0".into(), } }, sketch_types::PathSegment::ToPoint { base: sketch_types::BasePath { - from: Point2d { x: 21.0, y: 1.0 }, - to: Point2d { x: 21.0, y: 21.0 }, + from: Point2d { x: 210.0, y: 10.0 }, + to: Point2d { x: 210.0, y: 210.0 }, name: "side1".into(), } }, sketch_types::PathSegment::ToPoint { base: sketch_types::BasePath { - from: Point2d { x: 21.0, y: 21.0 }, - to: Point2d { x: 1.0, y: 21.0 }, + from: Point2d { x: 210.0, y: 210.0 }, + to: Point2d { x: 10.0, y: 210.0 }, name: "side2".into(), } }, sketch_types::PathSegment::ToPoint { base: sketch_types::BasePath { - from: Point2d { x: 1.0, y: 21.0 }, - to: Point2d { x: 1.0, y: 1.0 }, + from: Point2d { x: 10.0, y: 210.0 }, + to: Point2d { x: 10.0, y: 10.0 }, name: "side3".into(), } }, ] ); - // use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat, ModelingCmd}; + // use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat}; // let out = client + // .unwrap() // .run_command( // uuid::Uuid::new_v4().into(), // each_cmd::TakeSnapshot { @@ -1126,6 +1129,7 @@ async fn stdlib_cube_partial() { // other => panic!("wrong output: {other:?}"), // }; // let out: Vec = out.contents.into(); + // std::fs::write("image.png", out).unwrap(); } async fn test_client() -> Session {