diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-linux.png index 3be3bdca2..b5c2ca7cc 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png index a8780cb67..360d58bb4 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-2d-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-2d-1-Google-Chrome-linux.png index 4651ce7cf..a5b9c1823 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-2d-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-2d-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-3d-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-3d-1-Google-Chrome-linux.png index a233ad1b5..1dad6d6a8 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-3d-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-3d-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XY-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XY-1-Google-Chrome-linux.png index ab1937b9b..5297f525f 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XY-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XY-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png index b7e282e99..be0796636 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--YZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--YZ-1-Google-Chrome-linux.png index d8ba9551f..c9068226d 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--YZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--YZ-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-linux.png index 63dea965a..a4124f77b 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png index a32344fa0..56bf354c4 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png index 464f925a8..8bd41189d 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png differ diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index c33384d21..b7cf850ef 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "kcl-lib" -version = "0.1.66" +version = "0.1.67" dependencies = [ "anyhow", "approx", diff --git a/src/wasm-lib/kcl/Cargo.toml b/src/wasm-lib/kcl/Cargo.toml index 451ecbc64..fd53a94d5 100644 --- a/src/wasm-lib/kcl/Cargo.toml +++ b/src/wasm-lib/kcl/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kcl-lib" description = "KittyCAD Language implementation and tools" -version = "0.1.66" +version = "0.1.67" edition = "2021" license = "MIT" repository = "https://github.com/KittyCAD/modeling-app" diff --git a/src/wasm-lib/kcl/src/ast/types.rs b/src/wasm-lib/kcl/src/ast/types.rs index d2d0e6646..84da23734 100644 --- a/src/wasm-lib/kcl/src/ast/types.rs +++ b/src/wasm-lib/kcl/src/ast/types.rs @@ -1232,28 +1232,34 @@ impl CallExpression { source_ranges: vec![self.into()], }) })?; - let result = result.get_value()?; + let result = result.get_value()?; Ok(result) } FunctionKind::UserDefined => { let func = memory.get(&fn_name, self.into())?; - let result = func - .call_fn(fn_args, memory.clone(), ctx.clone()) - .await - .map_err(|e| { + let (result, global_memory_items) = + func.call_fn(fn_args, memory.clone(), ctx.clone()).await.map_err(|e| { // Add the call expression to the source ranges. e.add_source_ranges(vec![self.into()]) - })? - .ok_or_else(|| { - KclError::UndefinedValue(KclErrorDetails { - message: format!("Result of user-defined function {} is undefined", fn_name), - source_ranges: vec![self.into()], - }) })?; + let result = result.ok_or_else(|| { + KclError::UndefinedValue(KclErrorDetails { + message: format!("Result of user-defined function {} is undefined", fn_name), + source_ranges: vec![self.into()], + }) + })?; let result = result.get_value()?; + // Add the global memory items to the memory. + for (key, item) in global_memory_items { + // We don't care about errors here because any collisions + // would happened in the function call itself and already + // errored out. + memory.add(&key, item, self.into()).unwrap_or_default(); + } + Ok(result) } } diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index d6f01c559..05a11d866 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -100,6 +100,18 @@ impl ProgramMemory { }) .collect() } + + /// Get all TagDeclarators and TagIdentifiers in the memory. + pub fn get_tags(&self) -> HashMap { + self.root + .values() + .filter_map(|item| match item { + MemoryItem::TagDeclarator(t) => Some((t.name.to_string(), item.clone())), + MemoryItem::TagIdentifier(t) => Some((t.value.to_string(), item.clone())), + _ => None, + }) + .collect::>() + } } impl Default for ProgramMemory { @@ -525,14 +537,17 @@ impl std::hash::Hash for TagIdentifier { } } -pub type MemoryFunction = - fn( - s: Vec, - memory: ProgramMemory, - expression: Box, - metadata: Vec, - ctx: ExecutorContext, - ) -> std::pin::Pin, KclError>> + Send>>; +pub type MemoryFunction = fn( + s: Vec, + memory: ProgramMemory, + expression: Box, + metadata: Vec, + ctx: ExecutorContext, +) -> std::pin::Pin< + Box< + dyn std::future::Future, HashMap), KclError>> + Send, + >, +>; fn force_memory_function< F: Fn( @@ -541,7 +556,12 @@ fn force_memory_function< Box, Vec, ExecutorContext, - ) -> std::pin::Pin, KclError>> + Send>>, + ) -> std::pin::Pin< + Box< + dyn std::future::Future, HashMap), KclError>> + + Send, + >, + >, >( f: F, ) -> F { @@ -686,7 +706,7 @@ impl MemoryItem { args: Vec, memory: ProgramMemory, ctx: ExecutorContext, - ) -> Result, KclError> { + ) -> Result<(Option, HashMap), KclError> { let MemoryItem::Function { func, expression, meta } = &self else { return Err(KclError::Semantic(KclErrorDetails { message: "not a in memory function".to_string(), @@ -1500,7 +1520,16 @@ impl ExecutorContext { } FunctionKind::UserDefined => { if let Some(func) = memory.clone().root.get(&fn_name) { - let result = func.call_fn(args.clone(), memory.clone(), self.clone()).await?; + let (result, global_memory_items) = + func.call_fn(args.clone(), memory.clone(), self.clone()).await?; + + // Add the global memory items to the memory. + for (key, item) in global_memory_items { + // We don't care about errors here because any collisions + // would happened in the function call itself and already + // errored out. + memory.add(&key, item, call_expr.into()).unwrap_or_default(); + } memory.return_ = result; } else { @@ -1625,7 +1654,7 @@ impl ExecutorContext { .inner_execute(function_expression.body.clone(), &mut fn_memory, BodyType::Block) .await?; - Ok(result.return_) + Ok((result.return_, fn_memory.get_tags())) }) }, ); diff --git a/src/wasm-lib/tests/executor/inputs/global-tags.kcl b/src/wasm-lib/tests/executor/inputs/global-tags.kcl new file mode 100644 index 000000000..f71e7c4f3 --- /dev/null +++ b/src/wasm-lib/tests/executor/inputs/global-tags.kcl @@ -0,0 +1,149 @@ +// A mounting bracket for the Focusrite Scarlett Solo audio interface +// This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material + +// define constants in mm +const radius = 6.0 +const width = 144.0 +const length = 80.0 +const depth = 45.0 +const thk = 4 +const holeDiam = 5 +const tabLength = 25 +const tabWidth = 12 +const tabThk = 4 + +// define a rectangular shape func +fn rectShape = (pos, w, l) => { + const rr = startSketchOn('xy') + |> startProfileAt([pos[0] - (w / 2), pos[1] - (l / 2)], %) + |> lineTo([pos[0] + w / 2, pos[1] - (l / 2)], %, "edge01") + |> lineTo([pos[0] + w / 2, pos[1] + l / 2], %, "edge02") + |> lineTo([pos[0] - (w / 2), pos[1] + l / 2], %, "edge03") + |> close(%, "edge04") + return rr +} + +// define the bracket plane +const bracketPlane = { + plane: { + origin: { x: 0, y: length / 2 + thk, z: 0 }, + x_axis: { x: 1, y: 0, z: 0 }, + y_axis: { x: 0, y: 0, z: 1 }, + z_axis: { x: 0, y: -1, z: 0 } + } +} + +// build the bracket sketch around the body +fn bracketSketch = (w, d, t) => { + const s = startSketchOn(bracketPlane) + |> startProfileAt([-w / 2 - t, d + t], %) + |> lineTo([-w / 2 - t, -t], %, "edge1") + |> lineTo([w / 2 + t, -t], %, "edge2") + |> lineTo([w / 2 + t, d + t], %, "edge3") + |> lineTo([w / 2, d + t], %, "edge4") + |> lineTo([w / 2, 0], %, "edge5") + |> lineTo([-w / 2, 0], %, "edge6") + |> lineTo([-w / 2, d + t], %, "edge7") + |> close(%, "edge8") + return s +} + +// build the body of the bracket +const bracketBody = bracketSketch(width, depth, thk) + |> extrude(length + 2 * thk, %) + |> fillet({ + radius: radius, + tags: [ + getNextAdjacentEdge("edge7", %), + getNextAdjacentEdge("edge2", %), + getNextAdjacentEdge("edge3", %), + getNextAdjacentEdge("edge6", %) + ] + }, %) + +// define the tab plane +const tabPlane = { + plane: { + origin: { x: 0, y: 0, z: depth + thk }, + x_axis: { x: 1, y: 0, z: 0 }, + y_axis: { x: 0, y: 1, z: 0 }, + z_axis: { x: 0, y: 0, z: 1 } + } +} + +// build the tabs of the mounting bracket (right side) +const tabsR = startSketchOn(tabPlane) + |> startProfileAt([width / 2 + thk, length / 2 + thk], %) + |> line([tabWidth, -tabLength / 3], %, "edge11") + |> line([0, -tabLength / 3 * 2], %, "edge12") + |> line([-tabWidth, -tabLength / 3], %, "edge13") + |> close(%, "edge14") + |> hole(circle([ + width / 2 + thk + tabWidth / 2, + length / 2 + thk - (tabLength / (3 / 2)) + ], holeDiam / 2, %), %) + |> extrude(-tabThk, %) + |> fillet({ + radius: holeDiam / 2, + tags: [ + getNextAdjacentEdge("edge12", %), + getNextAdjacentEdge("edge13", %) + ] + }, %) + |> patternLinear3d({ + axis: [0, -1, 0], + repetitions: 1, + distance: length + 2 * thk - (tabLength * 4 / 3) + }, %) + +// build the tabs of the mounting bracket (left side) +const tabsL = startSketchOn(tabPlane) + |> startProfileAt([-width / 2 - thk, length / 2 + thk], %) + |> line([-tabWidth, -tabLength / 3], %, "edge21") + |> line([0, -tabLength / 3 * 2], %, "edge22") + |> line([tabWidth, -tabLength / 3], %, "edge23") + |> close(%, "edge24") + |> hole(circle([ + -width / 2 - thk - (tabWidth / 2), + length / 2 + thk - (tabLength / (3 / 2)) + ], holeDiam / 2, %), %) + |> extrude(-tabThk, %) + |> fillet({ + radius: holeDiam / 2, + tags: [ + getNextAdjacentEdge("edge21", %), + getNextAdjacentEdge("edge22", %) + ] + }, %) + |> patternLinear3d({ + axis: [0, -1, 0], + repetitions: 1, + distance: length + 2 * thk - (tabLength * 4 / 3) + }, %) + +// define a plane for retention bumps +const retPlane = { + plane: { + origin: { x: -width / 2 + 20, y: 0, z: 0 }, + x_axis: { x: 0, y: 1, z: 0 }, + y_axis: { x: 0, y: 0, z: 1 }, + z_axis: { x: 1, y: 0, z: 0 } + } +} + +// build the retention bump in the front +const retFront = startSketchOn(retPlane) + |> startProfileAt([-length / 2 - thk, 0], %) + |> line([0, thk], %) + |> line([thk, -thk], %) + |> close(%) + |> extrude(width - 40, %) + +// build the retention bump in the back +const retBack = startSketchOn(retPlane) + |> startProfileAt([length / 2 + thk, 0], %) + |> line([0, thk], %) + |> line([-thk, 0], %) + |> line([0, -thk], %) + |> close(%) + |> extrude(width - 40, %) diff --git a/src/wasm-lib/tests/executor/main.rs b/src/wasm-lib/tests/executor/main.rs index 7129ee46c..04db5c080 100644 --- a/src/wasm-lib/tests/executor/main.rs +++ b/src/wasm-lib/tests/executor/main.rs @@ -2457,3 +2457,10 @@ let p = triangle(200) r#"value already defined: KclErrorDetails { source_ranges: [SourceRange([317, 319]), SourceRange([332, 345])], message: "Cannot redefine `a`" }"# ); } + +#[tokio::test(flavor = "multi_thread")] +async fn serial_test_global_tags() { + let code = include_str!("inputs/global-tags.kcl"); + let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); + twenty_twenty::assert_image("tests/executor/outputs/global_tags.png", &result, 0.999); +} diff --git a/src/wasm-lib/tests/executor/outputs/global_tags.png b/src/wasm-lib/tests/executor/outputs/global_tags.png new file mode 100644 index 000000000..b684a83fa Binary files /dev/null and b/src/wasm-lib/tests/executor/outputs/global_tags.png differ