diff --git a/.codespellrc b/.codespellrc index afecf2a1a4..a3b1e13bdb 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,3 +1,3 @@ [codespell] -ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo +ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,AbsoluteY skip: **/target,node_modules,build,**/Cargo.lock diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index dd1fd8a0f5..23d59ceac4 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -1471,6 +1471,7 @@ dependencies = [ name = "grackle" version = "0.1.0" dependencies = [ + "image", "kcl-lib", "kittycad", "kittycad-execution-plan", @@ -1482,6 +1483,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "twenty-twenty", "uuid", ] @@ -2007,7 +2009,7 @@ dependencies = [ [[package]] name = "kittycad-execution-plan" version = "0.1.0" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" dependencies = [ "bytes", "insta", @@ -2027,7 +2029,7 @@ dependencies = [ [[package]] name = "kittycad-execution-plan-macros" version = "0.1.9" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" dependencies = [ "proc-macro2", "quote", @@ -2037,7 +2039,7 @@ dependencies = [ [[package]] name = "kittycad-execution-plan-traits" version = "0.1.13" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" dependencies = [ "serde", "thiserror", @@ -2046,8 +2048,8 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds" -version = "0.1.30" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" +version = "0.1.32" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" dependencies = [ "anyhow", "chrono", @@ -2075,7 +2077,7 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds-macros" version = "0.1.2" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" dependencies = [ "proc-macro2", "quote", @@ -2085,11 +2087,12 @@ dependencies = [ [[package]] name = "kittycad-modeling-session" version = "0.1.1" -source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4" +source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" dependencies = [ "futures", "kittycad", "kittycad-modeling-cmds", + "lsystem", "reqwest", "serde_json", "thiserror", @@ -2187,6 +2190,12 @@ dependencies = [ "url", ] +[[package]] +name = "lsystem" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c47210f2a9f5ae2073e7b847026e3233bcb012aa6845201c69c26481762a81" + [[package]] name = "matchit" version = "0.7.3" diff --git a/src/wasm-lib/grackle/Cargo.toml b/src/wasm-lib/grackle/Cargo.toml index 586900bddf..af08170e27 100644 --- a/src/wasm-lib/grackle/Cargo.toml +++ b/src/wasm-lib/grackle/Cargo.toml @@ -6,6 +6,7 @@ description = "A new executor for KCL which compiles to Execution Plans" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +image = { version = "0.24.7", default-features = false, features = ["png"] } kcl-lib = { path = "../kcl" } kittycad = { workspace = true } kittycad-execution-plan = { workspace = true } @@ -15,6 +16,7 @@ kittycad-modeling-cmds = { workspace = true } kittycad-modeling-session = { workspace = true } thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["macros", "rt"] } +twenty-twenty = "0.7.0" uuid = "1.7" [dev-dependencies] diff --git a/src/wasm-lib/grackle/fixtures/cube_lineTo.png b/src/wasm-lib/grackle/fixtures/cube_lineTo.png new file mode 100644 index 0000000000..6450d6688a Binary files /dev/null and b/src/wasm-lib/grackle/fixtures/cube_lineTo.png differ diff --git a/src/wasm-lib/grackle/fixtures/cube_xyLine.png b/src/wasm-lib/grackle/fixtures/cube_xyLine.png new file mode 100644 index 0000000000..2a04bd115e Binary files /dev/null and b/src/wasm-lib/grackle/fixtures/cube_xyLine.png differ diff --git a/src/wasm-lib/grackle/src/binding_scope.rs b/src/wasm-lib/grackle/src/binding_scope.rs index b383d538a8..465b1b1b0d 100644 --- a/src/wasm-lib/grackle/src/binding_scope.rs +++ b/src/wasm-lib/grackle/src/binding_scope.rs @@ -113,6 +113,26 @@ impl BindingScope { "lineTo".into(), EpBinding::from(KclFunction::LineTo(native_functions::sketch::LineTo)), ), + ( + "line".into(), + EpBinding::from(KclFunction::Line(native_functions::sketch::Line)), + ), + ( + "xLineTo".into(), + EpBinding::from(KclFunction::XLineTo(native_functions::sketch::XLineTo)), + ), + ( + "xLine".into(), + EpBinding::from(KclFunction::XLine(native_functions::sketch::XLine)), + ), + ( + "yLineTo".into(), + EpBinding::from(KclFunction::YLineTo(native_functions::sketch::YLineTo)), + ), + ( + "yLine".into(), + EpBinding::from(KclFunction::YLine(native_functions::sketch::YLine)), + ), ( "extrude".into(), EpBinding::from(KclFunction::Extrude(native_functions::sketch::Extrude)), diff --git a/src/wasm-lib/grackle/src/error.rs b/src/wasm-lib/grackle/src/error.rs index 143d194545..33a70447d0 100644 --- a/src/wasm-lib/grackle/src/error.rs +++ b/src/wasm-lib/grackle/src/error.rs @@ -76,7 +76,7 @@ impl From for Error { ) -> Self { Self::Execution { error, - instruction, + instruction: instruction.expect("no instruction"), instruction_index, } } diff --git a/src/wasm-lib/grackle/src/lib.rs b/src/wasm-lib/grackle/src/lib.rs index 40beaa5114..189f68b79e 100644 --- a/src/wasm-lib/grackle/src/lib.rs +++ b/src/wasm-lib/grackle/src/lib.rs @@ -262,6 +262,11 @@ impl Planner { 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::Line(f) => f.call(&mut ctx, args)?, + KclFunction::XLineTo(f) => f.call(&mut ctx, args)?, + KclFunction::XLine(f) => f.call(&mut ctx, args)?, + KclFunction::YLineTo(f) => f.call(&mut ctx, args)?, + KclFunction::YLine(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) => { @@ -631,6 +636,11 @@ enum KclFunction { Id(native_functions::Id), StartSketchAt(native_functions::sketch::StartSketchAt), LineTo(native_functions::sketch::LineTo), + Line(native_functions::sketch::Line), + XLineTo(native_functions::sketch::XLineTo), + XLine(native_functions::sketch::XLine), + YLineTo(native_functions::sketch::YLineTo), + YLine(native_functions::sketch::YLine), Add(native_functions::Add), UserDefined(UserDefinedFunction), Extrude(native_functions::sketch::Extrude), diff --git a/src/wasm-lib/grackle/src/native_functions/sketch.rs b/src/wasm-lib/grackle/src/native_functions/sketch.rs index 6394d6df0e..5ae32403f9 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::{Close, Extrude, LineTo, StartSketchAt}; +pub use stdlib_functions::{Close, Extrude, Line, LineTo, StartSketchAt, XLine, XLineTo, YLine, YLineTo}; diff --git a/src/wasm-lib/grackle/src/native_functions/sketch/helpers.rs b/src/wasm-lib/grackle/src/native_functions/sketch/helpers.rs index 58b377aabf..258314b2b8 100644 --- a/src/wasm-lib/grackle/src/native_functions/sketch/helpers.rs +++ b/src/wasm-lib/grackle/src/native_functions/sketch/helpers.rs @@ -139,7 +139,7 @@ pub fn sequence_binding( } } -/// Extract a 2D point from an argument to a Cabble. +/// Extract a 2D point from an argument to a KCL Function. pub fn arg_point2d( arg: EpBinding, fn_name: &'static str, @@ -148,7 +148,7 @@ pub fn arg_point2d( arg_number: usize, ) -> Result { let expected = "2D point (array with length 2)"; - let elements = sequence_binding(arg, "startSketchAt", "an array of length 2", arg_number)?; + let elements = sequence_binding(arg, fn_name, "an array of length 2", arg_number)?; if elements.len() != 2 { return Err(CompileError::ArgWrongType { fn_name, @@ -165,12 +165,12 @@ pub fn arg_point2d( let start_z = start + 2; instructions.extend([ Instruction::Copy { - source: single_binding(elements[0].clone(), "startSketchAt", "number", arg_number)?, + source: single_binding(elements[0].clone(), fn_name, "number", arg_number)?, destination: Destination::Address(start_x), length: 1, }, Instruction::Copy { - source: single_binding(elements[1].clone(), "startSketchAt", "number", arg_number)?, + source: single_binding(elements[1].clone(), fn_name, "number", arg_number)?, destination: Destination::Address(start_y), length: 1, }, 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 d9f6dd18e5..a00802e5a1 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 @@ -1,7 +1,7 @@ use kittycad_execution_plan::{ api_request::ApiRequest, sketch_types::{self, Axes, BasePath, Plane, SketchGroup}, - Destination, Instruction, + BinaryArithmetic, BinaryOperation, Destination, Instruction, Operand, }; use kittycad_execution_plan_traits::{Address, InMemory, Primitive, Value}; use kittycad_modeling_cmds::{ @@ -13,6 +13,22 @@ 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(PartialEq)] +pub enum At { + RelativeXY, + AbsoluteXY, + RelativeX, + AbsoluteX, + RelativeY, + AbsoluteY, +} + +impl At { + pub fn is_relative(&self) -> bool { + *self == At::RelativeX || *self == At::RelativeY || *self == At::RelativeXY + } +} + #[derive(Debug, Clone)] #[cfg_attr(test, derive(Eq, PartialEq))] pub struct Close; @@ -140,25 +156,124 @@ impl Callable for LineTo { &self, ctx: &mut crate::native_functions::Context<'_>, args: Vec, + ) -> Result { + LineBare::call(ctx, "lineTo", args, LineBareOptions { at: At::AbsoluteXY }) + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct Line; + +impl Callable for Line { + fn call( + &self, + ctx: &mut crate::native_functions::Context<'_>, + args: Vec, + ) -> Result { + LineBare::call(ctx, "line", args, LineBareOptions { at: At::RelativeXY }) + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct XLineTo; + +impl Callable for XLineTo { + fn call( + &self, + ctx: &mut crate::native_functions::Context<'_>, + args: Vec, + ) -> Result { + LineBare::call(ctx, "xLineTo", args, LineBareOptions { at: At::AbsoluteX }) + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct XLine; + +impl Callable for XLine { + fn call( + &self, + ctx: &mut crate::native_functions::Context<'_>, + args: Vec, + ) -> Result { + LineBare::call(ctx, "xLine", args, LineBareOptions { at: At::RelativeX }) + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct YLineTo; + +impl Callable for YLineTo { + fn call( + &self, + ctx: &mut crate::native_functions::Context<'_>, + args: Vec, + ) -> Result { + LineBare::call(ctx, "yLineTo", args, LineBareOptions { at: At::AbsoluteY }) + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct YLine; + +impl Callable for YLine { + fn call( + &self, + ctx: &mut crate::native_functions::Context<'_>, + args: Vec, + ) -> Result { + LineBare::call(ctx, "yLine", args, LineBareOptions { at: At::RelativeY }) + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +/// Exposes all the possible arguments the `line` modeling command can take. +/// Reduces code for the other line functions needed. +/// We do not expose this to the developer since it does not align with +/// the documentation (there is no "lineBare"). +pub struct LineBare; + +/// Used to configure the call to handle different line variants. +pub struct LineBareOptions { + /// Where to start coordinates at, ex: At::RelativeXY. + at: At, +} + +impl LineBare { + fn call( + ctx: &mut crate::native_functions::Context<'_>, + fn_name: &'static str, + args: Vec, + opts: LineBareOptions, ) -> Result { let mut instructions = Vec::new(); - let fn_name = "lineTo"; - // Get both required params. + + let required = 2; + let mut args_iter = args.into_iter(); + let Some(to) = args_iter.next() else { return Err(CompileError::NotEnoughArgs { fn_name: fn_name.into(), - required: 2, - actual: 0, + required, + actual: args_iter.count(), }); }; + let Some(sketch_group) = args_iter.next() else { return Err(CompileError::NotEnoughArgs { fn_name: fn_name.into(), - required: 2, - actual: 1, + required, + actual: args_iter.count(), }); }; + let tag = match args_iter.next() { Some(a) => a, None => { @@ -171,26 +286,90 @@ impl Callable for LineTo { EpBinding::Single(empty_string_addr) } }; + // Check the type of required params. - let to = arg_point2d(to, fn_name, &mut instructions, ctx, 0)?; + // We don't check `to` here because it can take on either a + // EpBinding::Sequence or EpBinding::Single. + let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?; let tag = single_binding(tag, fn_name, "string tag", 2)?; let id = Uuid::new_v4(); + // Start of the path segment (which is a straight line). let length_of_3d_point = Point3d::::default().into_parts().len(); let start_of_line = ctx.next_address.offset_by(1); + // Reserve space for the line's end, and the `relative: bool` field. ctx.next_address.offset_by(length_of_3d_point + 1); let new_sg_index = ctx.assign_sketch_group(); + + // Copy based on the options. + match opts { + LineBareOptions { at: At::AbsoluteXY, .. } | LineBareOptions { at: At::RelativeXY, .. } => { + // Push the `to` 2D point onto the stack. + let EpBinding::Sequence { elements, length_at: _ } = to.clone() else { + return Err(CompileError::InvalidOperand("Must pass a list of length 2")); + }; + let &[EpBinding::Single(el0), EpBinding::Single(el1)] = elements.as_slice() else { + return Err(CompileError::InvalidOperand("Must pass a sequence here.")); + }; + instructions.extend([ + Instruction::Copy { + // X + source: el0, + length: 1, + destination: Destination::StackPush, + }, + Instruction::Copy { + // Y + source: el1, + length: 1, + destination: Destination::StackExtend, + }, + Instruction::StackExtend { data: vec![0.0.into()] }, // Z + ]); + } + LineBareOptions { at: At::AbsoluteX, .. } | LineBareOptions { at: At::RelativeX, .. } => { + let EpBinding::Single(addr) = to else { + return Err(CompileError::InvalidOperand("Must pass a single value here.")); + }; + instructions.extend([ + Instruction::Copy { + // X + source: addr, + length: 1, + destination: Destination::StackPush, + }, + Instruction::StackExtend { + data: vec![Primitive::from(0.0)], + }, // Y + Instruction::StackExtend { + data: vec![Primitive::from(0.0)], + }, // Z + ]); + } + LineBareOptions { at: At::AbsoluteY, .. } | LineBareOptions { at: At::RelativeY, .. } => { + let EpBinding::Single(addr) = to else { + return Err(CompileError::InvalidOperand("Must pass a single value here.")); + }; + instructions.extend([ + Instruction::StackPush { + data: vec![Primitive::from(0.0)], + }, // X + Instruction::Copy { + // Y + source: addr, + length: 1, + destination: Destination::StackExtend, + }, + Instruction::StackExtend { + data: vec![Primitive::from(0.0)], + }, // Z + ]); + } + } + instructions.extend([ - // Push the `to` 2D point onto the stack. - Instruction::Copy { - source: to, - length: 2, - destination: Destination::StackPush, - }, - // Make it a 3D point. - Instruction::StackExtend { data: vec![0.0.into()] }, // Append the new path segment to memory. // First comes its tag. Instruction::SetPrimitive { @@ -204,7 +383,7 @@ impl Callable for LineTo { // Then its `relative` field. Instruction::SetPrimitive { address: start_of_line + 1 + length_of_3d_point, - value: false.into(), + value: opts.at.is_relative().into(), }, // Push the path ID onto the stack. Instruction::SketchGroupCopyFrom { @@ -231,16 +410,159 @@ impl Callable for LineTo { data: vec![Primitive::from("ToPoint".to_owned())], }, // `BasePath::from` point. + // Place them in the secondary stack to prepare ToPoint structure. Instruction::SketchGroupGetLastPoint { source: sg, destination: Destination::StackExtend, }, - // `BasePath::to` point. - Instruction::Copy { - source: start_of_line + 1, - length: 2, - destination: Destination::StackExtend, + ]); + + // Reserve space for the segment last point + let to_point_from = ctx.next_address.offset_by(2); + + instructions.extend([ + // Copy to the primary stack as well to be worked with. + Instruction::SketchGroupGetLastPoint { + source: sg, + destination: Destination::Address(to_point_from), }, + ]); + + // `BasePath::to` point. + + // The copy here depends on the incoming `to` data. + // Sometimes it's a list, sometimes it's single datum. + // And the relative/not relative matters. When relative, we need to + // copy coords from `from` into the new `to` coord that don't change. + // At least everything else can be built up from these "primitives". + if let EpBinding::Sequence { elements, length_at: _ } = to.clone() { + if let &[EpBinding::Single(el0), EpBinding::Single(el1)] = elements.as_slice() { + match opts { + // ToPoint { from: { x1, y1 }, to: { x1 + x2, y1 + y2 } } + LineBareOptions { at: At::RelativeXY, .. } => { + instructions.extend([ + Instruction::BinaryArithmetic { + arithmetic: BinaryArithmetic { + operation: BinaryOperation::Add, + operand0: Operand::Reference(to_point_from + 0), + operand1: Operand::Reference(el0), + }, + destination: Destination::StackExtend, + }, + Instruction::BinaryArithmetic { + arithmetic: BinaryArithmetic { + operation: BinaryOperation::Add, + operand0: Operand::Reference(to_point_from + 1), + operand1: Operand::Reference(el1), + }, + destination: Destination::StackExtend, + }, + ]); + } + // ToPoint { from: { x1, y1 }, to: { x2, y2 } } + LineBareOptions { at: At::AbsoluteXY, .. } => { + // Otherwise just directly copy the new points. + instructions.extend([ + Instruction::Copy { + source: el0, + length: 1, + destination: Destination::StackExtend, + }, + Instruction::Copy { + source: el1, + length: 1, + destination: Destination::StackExtend, + }, + ]); + } + _ => { + return Err(CompileError::InvalidOperand( + "A Sequence with At::...X or At::...Y is not valid here. Must be At::...XY.", + )); + } + } + } + } else if let EpBinding::Single(addr) = to { + match opts { + // ToPoint { from: { x1, y1 }, to: { x1 + x2, y1 } } + LineBareOptions { at: At::RelativeX } => { + instructions.extend([ + Instruction::BinaryArithmetic { + arithmetic: BinaryArithmetic { + operation: BinaryOperation::Add, + operand0: Operand::Reference(to_point_from + 0), + operand1: Operand::Reference(addr), + }, + destination: Destination::StackExtend, + }, + Instruction::Copy { + source: to_point_from + 1, + length: 1, + destination: Destination::StackExtend, + }, + ]); + } + // ToPoint { from: { x1, y1 }, to: { x2, y1 } } + LineBareOptions { at: At::AbsoluteX } => { + instructions.extend([ + Instruction::Copy { + source: addr, + length: 1, + destination: Destination::StackExtend, + }, + Instruction::Copy { + source: to_point_from + 1, + length: 1, + destination: Destination::StackExtend, + }, + ]); + } + // ToPoint { from: { x1, y1 }, to: { x1, y1 + y2 } } + LineBareOptions { at: At::RelativeY } => { + instructions.extend([ + Instruction::Copy { + source: to_point_from + 0, + length: 1, + destination: Destination::StackExtend, + }, + Instruction::BinaryArithmetic { + arithmetic: BinaryArithmetic { + operation: BinaryOperation::Add, + operand0: Operand::Reference(to_point_from + 1), + operand1: Operand::Reference(addr), + }, + destination: Destination::StackExtend, + }, + ]); + } + // ToPoint { from: { x1, y1 }, to: { x1, y2 } } + LineBareOptions { at: At::AbsoluteY } => { + instructions.extend([ + Instruction::Copy { + source: to_point_from + 0, + length: 1, + destination: Destination::StackExtend, + }, + Instruction::Copy { + source: addr, + length: 1, + destination: Destination::StackExtend, + }, + ]); + } + _ => { + return Err(CompileError::InvalidOperand( + "A Single binding with At::...XY is not valid here.", + )); + } + } + } else { + return Err(CompileError::InvalidOperand( + "Must be a sequence or single value binding.", + )); + } + + instructions.extend([ // `BasePath::name` string. Instruction::Copy { source: tag, diff --git a/src/wasm-lib/grackle/src/tests.rs b/src/wasm-lib/grackle/src/tests.rs index 3902cd9d84..57604592f2 100644 --- a/src/wasm-lib/grackle/src/tests.rs +++ b/src/wasm-lib/grackle/src/tests.rs @@ -1048,14 +1048,10 @@ fn store_object_with_array_property() { /// Write the program's plan to the KCVM debugger's normal input file. #[allow(unused)] -fn kcvm_dbg(kcl_program: &str) { +fn kcvm_dbg(kcl_program: &str, path: &str) { let (plan, _scope, _) = must_plan(kcl_program); let plan_json = serde_json::to_string_pretty(&plan).unwrap(); - std::fs::write( - "/Users/adamchalmers/kc-repos/modeling-api/execution-plan-debugger/test_input.json", - plan_json, - ) - .unwrap(); + std::fs::write(path, plan_json).unwrap(); } #[tokio::test] @@ -1069,8 +1065,6 @@ async fn stdlib_cube_partial() { |> close(%) |> extrude(100.0, %) "#; - let (_plan, _scope, last_address) = must_plan(program); - assert_eq!(last_address, Address::ZERO + 66); let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) .ast() .unwrap(); @@ -1113,23 +1107,115 @@ async fn stdlib_cube_partial() { }, ] ); - // use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat}; - // let out = client - // .unwrap() - // .run_command( - // uuid::Uuid::new_v4().into(), - // each_cmd::TakeSnapshot { - // format: ImageFormat::Png, - // }, - // ) - // .await - // .unwrap(); - // let out = match out { - // OkModelingCmdResponse::TakeSnapshot(b) => b, - // other => panic!("wrong output: {other:?}"), - // }; - // let out: Vec = out.contents.into(); - // std::fs::write("image.png", out).unwrap(); + use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat}; + let out = client + .unwrap() + .run_command( + uuid::Uuid::new_v4().into(), + kittycad_modeling_cmds::ModelingCmd::from(each_cmd::TakeSnapshot { + format: ImageFormat::Png, + }), + ) + .await + .unwrap(); + + let out = match out { + OkModelingCmdResponse::TakeSnapshot(kittycad_modeling_cmds::output::TakeSnapshot { contents: b }) => b, + other => panic!("wrong output: {other:?}"), + }; + + use image::io::Reader as ImageReader; + let img = ImageReader::new(std::io::Cursor::new(out)) + .with_guessed_format() + .unwrap() + .decode() + .unwrap(); + twenty_twenty::assert_image("fixtures/cube_lineTo.png", &img, 0.9999); +} + +#[tokio::test] +async fn stdlib_cube_xline_yline() { + let program = r#" + let cube = startSketchAt([0.0, 0.0], "adam") + |> xLine(210.0, %, "side0") + |> yLine(210.0, %, "side1") + |> xLine(-210.0, %, "side2") + |> yLine(-210.0, %, "side3") + |> close(%) + |> extrude(100.0, %) + "#; + kcvm_dbg( + program, + "/home/lee/Code/Zoo/modeling-api/execution-plan-debugger/cube_xyline.json", + ); + let (_plan, _scope, _last_address) = must_plan(program); + + let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) + .ast() + .unwrap(); + let mut client = Some(test_client().await); + let mem = match crate::execute(ast, &mut client).await { + Ok(mem) => mem, + Err(e) => panic!("{e}"), + }; + let sg = &mem.sketch_groups.last().unwrap(); + assert_eq!( + sg.path_rest, + vec![ + sketch_types::PathSegment::ToPoint { + base: sketch_types::BasePath { + from: Point2d { x: 0.0, y: 0.0 }, + to: Point2d { x: 210.0, y: 0.0 }, + name: "side0".into(), + } + }, + sketch_types::PathSegment::ToPoint { + base: sketch_types::BasePath { + from: Point2d { x: 210.0, y: 0.0 }, + to: Point2d { x: 210.0, y: 210.0 }, + name: "side1".into(), + } + }, + sketch_types::PathSegment::ToPoint { + base: sketch_types::BasePath { + from: Point2d { x: 210.0, y: 210.0 }, + to: Point2d { x: 0.0, y: 210.0 }, + name: "side2".into(), + } + }, + sketch_types::PathSegment::ToPoint { + base: sketch_types::BasePath { + from: Point2d { x: 0.0, y: 210.0 }, + to: Point2d { x: 0.0, y: 0.0 }, + name: "side3".into(), + } + }, + ] + ); + use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat}; + let out = client + .unwrap() + .run_command( + uuid::Uuid::new_v4().into(), + kittycad_modeling_cmds::ModelingCmd::from(each_cmd::TakeSnapshot { + format: ImageFormat::Png, + }), + ) + .await + .unwrap(); + + let out = match out { + OkModelingCmdResponse::TakeSnapshot(kittycad_modeling_cmds::output::TakeSnapshot { contents: b }) => b, + other => panic!("wrong output: {other:?}"), + }; + + use image::io::Reader as ImageReader; + let img = ImageReader::new(std::io::Cursor::new(out)) + .with_guessed_format() + .unwrap() + .decode() + .unwrap(); + twenty_twenty::assert_image("fixtures/cube_xyLine.png", &img, 0.9999); } async fn test_client() -> Session {