diff --git a/e2e/playwright/projects.spec.ts b/e2e/playwright/projects.spec.ts index 279775b59..a280996c0 100644 --- a/e2e/playwright/projects.spec.ts +++ b/e2e/playwright/projects.spec.ts @@ -1497,6 +1497,7 @@ test( 'i_shape.kcl', 'kittycad_svg.kcl', 'lego.kcl', + 'lsystem.kcl', 'math.kcl', 'member_expression_sketch.kcl', 'mike_stress_test.kcl', diff --git a/e2e/playwright/testing-settings.spec.ts b/e2e/playwright/testing-settings.spec.ts index cf7cbab69..3e59820a1 100644 --- a/e2e/playwright/testing-settings.spec.ts +++ b/e2e/playwright/testing-settings.spec.ts @@ -286,11 +286,12 @@ test.describe('Testing settings', () => { await page.setViewportSize({ width: 1200, height: 500 }) // Selectors and constants - const tempSettingsFilePath = join( + const tempProjectSettingsFilePath = join( projectDirName, projectName, PROJECT_SETTINGS_FILE_NAME ) + const tempUserSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) const userThemeColor = '120' const projectThemeColor = '50' const settingsOpenButton = page.getByRole('link', { @@ -311,6 +312,12 @@ test.describe('Testing settings', () => { await themeColorSetting.fill(userThemeColor) await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor) await settingsCloseButton.click() + await expect + .poll(async () => fsp.readFile(tempUserSettingsFilePath, 'utf-8'), { + message: 'Setting should now be written to the file', + timeout: 5_000, + }) + .toContain(`themeColor = "${userThemeColor}"`) }) await test.step('Set project theme color', async () => { @@ -324,10 +331,13 @@ test.describe('Testing settings', () => { await settingsCloseButton.click() // Make sure that the project settings file has been written to before continuing await expect - .poll(async () => fsp.readFile(tempSettingsFilePath, 'utf-8'), { - message: 'Setting should now be written to the file', - timeout: 5_000, - }) + .poll( + async () => fsp.readFile(tempProjectSettingsFilePath, 'utf-8'), + { + message: 'Setting should now be written to the file', + timeout: 5_000, + } + ) .toContain(`themeColor = "${projectThemeColor}"`) }) @@ -341,6 +351,7 @@ test.describe('Testing settings', () => { await test.step(`Navigate back to the home view and see user setting applied`, async () => { await logoLink.click() + await page.screenshot({ path: 'out.png' }) await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor) }) diff --git a/src/wasm-lib/kcl/benches/compiler_benchmark_criterion.rs b/src/wasm-lib/kcl/benches/compiler_benchmark_criterion.rs index 534bb262d..f913a2665 100644 --- a/src/wasm-lib/kcl/benches/compiler_benchmark_criterion.rs +++ b/src/wasm-lib/kcl/benches/compiler_benchmark_criterion.rs @@ -13,6 +13,7 @@ pub fn bench_parse(c: &mut Criterion) { ("cube", CUBE_PROGRAM), ("math", MATH_PROGRAM), ("mike_stress_test", MIKE_STRESS_TEST_PROGRAM), + ("koch snowflake", LSYSTEM_KOCH_SNOWFLAKE_PROGRAM), ] { let tokens = kcl_lib::token::lexer(file).unwrap(); c.bench_function(&format!("parse_{name}"), move |b| { @@ -37,3 +38,4 @@ const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_p const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl"); const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl"); const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../../tests/executor/inputs/mike_stress_test.kcl"); +const LSYSTEM_KOCH_SNOWFLAKE_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl"); diff --git a/src/wasm-lib/kcl/benches/compiler_benchmark_iai.rs b/src/wasm-lib/kcl/benches/compiler_benchmark_iai.rs index bdaf5d0c3..c1d27c8d4 100644 --- a/src/wasm-lib/kcl/benches/compiler_benchmark_iai.rs +++ b/src/wasm-lib/kcl/benches/compiler_benchmark_iai.rs @@ -19,6 +19,9 @@ fn lex_cube() { fn lex_math() { black_box(kcl_lib::token::lexer(MATH_PROGRAM).unwrap()); } +fn lex_lsystem() { + black_box(kcl_lib::token::lexer(LSYSTEM_PROGRAM).unwrap()); +} fn parse_kitt() { parse(KITT_PROGRAM) @@ -32,19 +35,25 @@ fn parse_cube() { fn parse_math() { parse(MATH_PROGRAM) } +fn parse_lsystem() { + parse(LSYSTEM_PROGRAM) +} iai::main! { lex_kitt, lex_pipes, lex_cube, lex_math, + lex_lsystem, parse_kitt, parse_pipes, parse_cube, parse_math, + parse_lsystem, } const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl"); const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl"); const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl"); const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl"); +const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl"); diff --git a/src/wasm-lib/kcl/benches/digest_benchmark.rs b/src/wasm-lib/kcl/benches/digest_benchmark.rs index 65e60de4d..91b8e949c 100644 --- a/src/wasm-lib/kcl/benches/digest_benchmark.rs +++ b/src/wasm-lib/kcl/benches/digest_benchmark.rs @@ -7,6 +7,7 @@ pub fn bench_digest(c: &mut Criterion) { ("cube", CUBE_PROGRAM), ("math", MATH_PROGRAM), ("mike_stress_test", MIKE_STRESS_TEST_PROGRAM), + ("lsystem", LSYSTEM_PROGRAM), ] { let tokens = kcl_lib::token::lexer(file).unwrap(); let prog = kcl_lib::parser::Parser::new(tokens).ast().unwrap(); @@ -29,3 +30,4 @@ const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_p const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl"); const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl"); const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../../tests/executor/inputs/mike_stress_test.kcl"); +const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl"); diff --git a/src/wasm-lib/kcl/benches/executor_benchmark_criterion.rs b/src/wasm-lib/kcl/benches/executor_benchmark_criterion.rs index 613c16436..d2a48a114 100644 --- a/src/wasm-lib/kcl/benches/executor_benchmark_criterion.rs +++ b/src/wasm-lib/kcl/benches/executor_benchmark_criterion.rs @@ -8,6 +8,7 @@ pub fn bench_execute(c: &mut Criterion) { ("cube", CUBE_PROGRAM), ("server_rack_lite", SERVER_RACK_LITE_PROGRAM), ("server_rack_heavy", SERVER_RACK_HEAVY_PROGRAM), + ("lsystem", LSYSTEM_PROGRAM), ] { let mut group = c.benchmark_group("executor"); // Configure Criterion.rs to detect smaller differences and increase sample size to improve @@ -52,3 +53,4 @@ const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl"); const SERVER_RACK_HEAVY_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-heavy.kcl"); const SERVER_RACK_LITE_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-lite.kcl"); const LEGO_PROGRAM: &str = include_str!("../../tests/executor/inputs/slow_lego.kcl.tmpl"); +const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl"); diff --git a/src/wasm-lib/kcl/benches/lsp_semantic_tokens_benchmark_criterion.rs b/src/wasm-lib/kcl/benches/lsp_semantic_tokens_benchmark_criterion.rs index 22dba2d18..d74b95c05 100644 --- a/src/wasm-lib/kcl/benches/lsp_semantic_tokens_benchmark_criterion.rs +++ b/src/wasm-lib/kcl/benches/lsp_semantic_tokens_benchmark_criterion.rs @@ -42,6 +42,7 @@ fn bench_kcl_lsp_semantic_tokens(c: &mut Criterion) { ("math", MATH_PROGRAM), ("mike_stress_test", MIKE_STRESS_TEST_PROGRAM), ("global_tags", GLOBAL_TAGS_FILE), + ("lsystem", LSYSTEM_PROGRAM), ] { c.bench_with_input(BenchmarkId::new("semantic_tokens_", name), &code, |b, &s| { let rt = Runtime::new().unwrap(); @@ -63,3 +64,4 @@ const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl"); const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl"); const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../../tests/executor/inputs/mike_stress_test.kcl"); const GLOBAL_TAGS_FILE: &str = include_str!("../../tests/executor/inputs/global-tags.kcl"); +const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl"); diff --git a/src/wasm-lib/tests/executor/inputs/lsystem.kcl b/src/wasm-lib/tests/executor/inputs/lsystem.kcl new file mode 100644 index 000000000..51b4fe861 --- /dev/null +++ b/src/wasm-lib/tests/executor/inputs/lsystem.kcl @@ -0,0 +1,121 @@ +// L-System KCL +// Zoo Corporation ⓒ 2024 + +// Comparators + +fn cond = (bools) => { + return (a, b) => { + x = int(min(max(-1, a-b), 1) + 1) + return bools[x] + } +} + +fn Not = (b) => { return if b { false } else { true } } +fn And = (a, b) => { return if a { if b { true } else { false } } else { false }} +fn Or = (a, b) => { return if a { true } else { if b { true } else { false }}} + +Eq = cond([false, true, false]) +Lt = cond([true, false, false]) +Gt = cond([false, false, true]) + +fn Lte = (a, b) => { return Not(Gt(a, b)) } +fn Gte = (a, b) => { return Not(Lt(a, b)) } + +// L-system +// Note: it was most concise to encode productions directly in axioms. +// Change them as you need. + +deg = pi()*2 / 360 + +fn setSketch = (state, _) => { + return { + depthMax: state.depthMax, + depth: state.depth + 1, + currentLength: state.currentLength, + factor: state.factor, + currentAngle: state.currentAngle, + angle: state.angle, + _: _ + } +} + +fn setDepth = (state, _) => { + return { + depthMax: state.depthMax, + depth: _, + currentLength: state.currentLength, + factor: state.factor, + currentAngle: state.currentAngle, + angle: state.angle, + _: state._ + } +} + +fn setAngle = (state, _) => { + return { + depthMax: state.depthMax, + depth: state.depth, + currentLength: state.currentLength, + factor: state.factor, + currentAngle: _, + angle: state.angle, + _: state._ + } +} + +fn setLength = (state, _) => { + return { + depthMax: state.depthMax, + depth: state.depth, + currentLength: _, + factor: state.factor, + currentAngle: state.currentAngle, + angle: state.angle, + _: state._ + } +} + +fn Gt2 = (state) => { return setLength(state, state.currentLength * state.factor) } +fn Lt2 = (state) => { return setLength(state, state.currentLength / state.factor) } +fn Add = (state) => { return setAngle(state, rem(int(state.currentAngle - state.angle), 360)) } +fn Sub = (state) => { return setAngle(state, rem(int(state.currentAngle + state.angle), 360)) } + +// Only necessary to get around recursion limitations... +fn F = (state, F) => { + return if Lt(state.depth, state.depthMax) { + stateNext = state |> setDepth(%, state.depth + 1) + + // Produce + // Note:if you need [ and ], just save state to a variable. + stateNext + |> F(%, F) |> Sub(%) |> F(%, F) + |> Add(%) |> Add(%) + |> F(%, F) |> Sub(%) |> F(%, F) + |> setDepth(%, stateNext.depth - 1) + + } else { + // Pass onto the next instruction + state |> setSketch(%, angledLine({ angle: state.currentAngle, length: state.currentLength }, state._)) + } +} + +fn LSystem = (args, axioms) => { + return axioms({ + depthMax: args.iterations, + depth: 0, + currentLength: 1.0, + factor: args.factor, + currentAngle: 0, + angle: args.angle, + _: startSketchAt([0, 0]), + }) +} + +LSystem({ + iterations: 1, + factor: 1.36, + angle: 60, +}, (_) => { + result = _ |> F(%, F) |> Add(%) |> Add(%) |> F(%, F) |> Add(%) |> Add(%) |> F(%, F) + return result._ +}) diff --git a/src/wasm-lib/tests/executor/main.rs b/src/wasm-lib/tests/executor/main.rs index 1131a97dd..986fc8e09 100644 --- a/src/wasm-lib/tests/executor/main.rs +++ b/src/wasm-lib/tests/executor/main.rs @@ -121,6 +121,14 @@ async fn kcl_test_execute_kittycad_svg() { assert_out("kittycad_svg", &result); } +#[tokio::test(flavor = "multi_thread")] +async fn kcl_test_execute_lsystem() { + let code = kcl_input!("lsystem"); + + let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); + assert_out("lsystem", &result); +} + #[tokio::test(flavor = "multi_thread")] async fn kcl_test_member_expression_sketch() { let code = kcl_input!("member_expression_sketch");