Compare commits

..

3 Commits

27 changed files with 102 additions and 1813 deletions

View File

@ -1490,6 +1490,7 @@ test(
'function_sketch.kcl',
'function_sketch_with_position.kcl',
'global-tags.kcl',
'helix_ccw.kcl',
'helix_defaults.kcl',
'helix_defaults_negative_extrude.kcl',
'helix_with_length.kcl',

View File

@ -1,5 +1,6 @@
import { test, expect } from '@playwright/test'
import * as fsp from 'fs/promises'
import * as fs from 'fs'
import { join } from 'path'
import {
getUtils,
@ -304,6 +305,21 @@ test.describe('Testing settings', () => {
const projectLink = page.getByText('bracket')
const logoLink = page.getByTestId('app-logo')
async function confirmThemeWasWritten(filePath: string, value: string) {
return expect
.poll(
async () => {
const fileExists = await fs.existsSync(filePath)
return fileExists ? fsp.readFile(filePath, 'utf-8') : ''
},
{
message: 'Setting should now be written to the file',
timeout: 5_000,
}
)
.toContain(`themeColor = "${value}"`)
}
await test.step('Set user theme color on home', async () => {
await expect(settingsOpenButton).toBeVisible()
await settingsOpenButton.click()
@ -311,14 +327,8 @@ test.describe('Testing settings', () => {
await expect(userSettingsTab).toBeChecked()
await themeColorSetting.fill(userThemeColor)
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
await confirmThemeWasWritten(tempUserSettingsFilePath, 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}"`)
// Only close the button after we've confirmed
})
await test.step('Set project theme color', async () => {
@ -329,29 +339,25 @@ test.describe('Testing settings', () => {
await expect(projectSettingsTab).toBeChecked()
await themeColorSetting.fill(projectThemeColor)
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
await settingsCloseButton.click()
// Make sure that the project settings file has been written to before continuing
await expect
.poll(
async () => fsp.readFile(tempProjectSettingsFilePath, 'utf-8'),
{
message: 'Setting should now be written to the file',
timeout: 5_000,
}
)
.toContain(`themeColor = "${projectThemeColor}"`)
await confirmThemeWasWritten(
tempProjectSettingsFilePath,
projectThemeColor
)
await settingsCloseButton.click()
})
await test.step('Refresh the application and see project setting applied', async () => {
// Make sure we're done navigating before we reload
await expect(settingsCloseButton).not.toBeVisible()
await page.reload({ waitUntil: 'domcontentloaded' })
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
})
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)
})

View File

@ -138,15 +138,15 @@ const FileTreeItem = ({
// the ReactNodes are destroyed, so is this listener :)
useFileSystemWatcher(
async (eventType, path) => {
// Prevents a cyclic read / write causing editor problems such as
// misplaced cursor positions.
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
return
}
// Don't try to read a file that was removed.
if (isCurrentFile && eventType !== 'unlink') {
// Prevents a cyclic read / write causing editor problems such as
// misplaced cursor positions.
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
return
}
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
code = normalizeLineEndings(code)
codeManager.updateCodeStateEditor(code)
@ -194,11 +194,11 @@ const FileTreeItem = ({
// Show the renaming form
addCurrentItemToRenaming()
} else if (e.code === 'Space') {
void handleClick()
handleClick()
}
}
async function handleClick() {
function handleClick() {
if (fileOrDir.children !== null) return // Don't open directories
if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) {
@ -208,10 +208,12 @@ const FileTreeItem = ({
`import("${fileOrDir.path.replace(project.path, '.')}")\n` +
codeManager.code
)
await codeManager.writeToFile()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.writeToFile()
// Prevent seeing the model built one piece at a time when changing files
await kclManager.executeCode(true)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
kclManager.executeCode(true)
} else {
// Let the lsp servers know we closed a file.
onFileClose(currentFile?.path || null, project?.path || null)
@ -240,7 +242,7 @@ const FileTreeItem = ({
style={{ paddingInlineStart: getIndentationCSS(level) }}
onClick={(e) => {
e.currentTarget.focus()
void handleClick()
handleClick()
}}
onKeyUp={handleKeyUp}
>
@ -499,13 +501,6 @@ export const FileTreeInner = ({
const isCurrentFile = loaderData.file?.path === path
const hasChanged = eventType === 'change'
if (isCurrentFile && hasChanged) return
// If it's a settings file we wrote to already from the app ignore it.
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
return
}
fileSend({ type: 'Refresh' })
},
[loaderData?.project?.path, fileContext.selectedDirectory.path].filter(

View File

@ -41,7 +41,6 @@ import { reportRejection } from 'lib/trap'
import { getAppSettingsFilePath } from 'lib/desktop'
import { isDesktop } from 'lib/isDesktop'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { codeManager } from 'lib/singletons'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -201,13 +200,13 @@ export const SettingsAuthProviderBase = ({
console.error('Error executing AST after settings change', e)
}
},
async persistSettings({ context, event }) {
persistSettings: ({ context, event }) => {
// Without this, when a user changes the file, it'd
// create a detection loop with the file-system watcher.
if (event.doNotPersist) return
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true
return saveSettings(context, loadedProject?.project?.path)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
saveSettings(context, loadedProject?.project?.path)
},
},
}),
@ -221,7 +220,7 @@ export const SettingsAuthProviderBase = ({
}, [])
useFileSystemWatcher(
async (eventType: string) => {
async () => {
// If there is a projectPath but it no longer exists it means
// it was exterally removed. If we let the code past this condition
// execute it will recreate the directory due to code in
@ -235,9 +234,6 @@ export const SettingsAuthProviderBase = ({
}
}
// Only reload if there are changes. Ignore everything else.
if (eventType !== 'change') return
const data = await loadAndValidateSettings(loadedProject?.project?.path)
settingsSend({
type: 'Set all settings',

View File

@ -96,10 +96,10 @@ export class KclPlugin implements PluginValue {
const newCode = viewUpdate.state.doc.toString()
codeManager.code = newCode
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.writeToFile()
void codeManager.writeToFile().then(() => {
this.scheduleUpdateDoc()
})
this.scheduleUpdateDoc()
}
scheduleUpdateDoc() {

View File

@ -26,7 +26,6 @@ export function useRefreshSettings(routeId: string = PATHS.INDEX) {
ctx.settings.send({
type: 'Set all settings',
settings: routeData,
doNotPersist: true,
})
}, [])
}

View File

@ -429,9 +429,13 @@ export class KclManager {
// Update the code state and the editor.
codeManager.updateCodeStateEditor(code)
// Write back to the file system.
void codeManager.writeToFile().then(() => this.executeCode())
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.writeToFile()
// execute the code.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.executeCode()
}
// There's overlapping responsibility between updateAst and executeAst.
// updateAst was added as it was used a lot before xState migration so makes the port easier.

View File

@ -121,28 +121,20 @@ export default class CodeManager {
// Only write our buffer contents to file once per second. Any faster
// and file-system watchers which read, will receive empty data during
// writes.
clearTimeout(this.timeoutWriter)
this.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true
return new Promise((resolve, reject) => {
this.timeoutWriter = setTimeout(() => {
if (!this._currentFilePath)
return reject(new Error('currentFilePath not set'))
// Wait one event loop to give a chance for params to be set
// Save the file to disk
this.timeoutWriter = setTimeout(() => {
// Wait one event loop to give a chance for params to be set
// Save the file to disk
this._currentFilePath &&
window.electron
.writeFile(this._currentFilePath, this.code ?? '')
.then(resolve)
.catch((err: Error) => {
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err)
toast.error('Error saving file, please check file permissions')
reject(err)
})
}, 1000)
})
}, 1000)
} else {
safeLSSetItem(PERSIST_CODE_KEY, this.code)
}

View File

@ -178,7 +178,6 @@ export async function loadAndValidateSettings(
if (err(appSettingsPayload)) return Promise.reject(appSettingsPayload)
let settingsNext = createSettings()
// Because getting the default directory is async, we need to set it after
if (onDesktop) {
settings.app.projectDirectory.default = await getInitialDefaultDir()

View File

@ -13,14 +13,14 @@ redo-kcl-stdlib-docs:
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
# Create a new KCL deterministic simulation test case.
new-sim-test test_name kcl_program render_to_png="true":
new-sim-test test_name kcl_program:
# Each test file gets its own directory. This will contain the KCL program, and its
# snapshotted artifacts (e.g. serialized tokens, serialized ASTs, program memory,
# PNG snapshots, etc).
mkdir kcl/tests/{{test_name}}
echo "{{kcl_program}}" > kcl/tests/{{test_name}}/input.kcl
# Add the various tests for this new test case.
cat kcl/tests/simtest.tmpl | sed "s/TEST_NAME_HERE/{{test_name}}/" | sed "s/RENDER_TO_PNG/{{render_to_png}}/" >> kcl/src/simulation_tests.rs
cat kcl/tests/simtest.tmpl | sed "s/TEST_NAME_HERE/{{test_name}}/" >> kcl/src/tests.rs
# Run all the tests for the first time, in the right order.
{{cita}} -p kcl-lib -- tests::{{test_name}}::tokenize
{{cita}} -p kcl-lib -- tests::{{test_name}}::parse

View File

@ -2008,27 +2008,22 @@ pub fn new_zoo_client(token: Option<String>, engine_addr: Option<String>) -> Res
// Create the client.
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
// Set an engine address if it's set.
let zoo_host = std::env::var("ZOO_HOST").ok();
let kittycad_host_env = std::env::var("KITTYCAD_HOST").ok();
let local_engine_addr_env = std::env::var("LOCAL_ENGINE_ADDR").ok();
let hosts_set = [&zoo_host, &kittycad_host_env, &local_engine_addr_env]
.iter()
.filter(|h| h.is_some())
.count();
if hosts_set > 1 {
return Err(anyhow::anyhow!(
"Conflicting host environment variables KITTYCAD_HOST, ZOO_HOST, LOCAL_ENGINE_ADDR were set. Use only one.",
));
}
let host_env = zoo_host.or(kittycad_host_env.or(local_engine_addr_env));
let kittycad_host_env = std::env::var("KITTYCAD_HOST");
if let Some(addr) = engine_addr {
println!("set base url {:?}", addr);
client.set_base_url(addr);
} else if let Some(addr) = host_env {
} else if let Ok(addr) = std::env::var("ZOO_HOST") {
if let Ok(kittycad_host) = kittycad_host_env {
if kittycad_host != addr {
return Err(anyhow::anyhow!(
"Both environment variables KITTYCAD_HOST=`{}` and ZOO_HOST=`{}` are set. Use only one.",
kittycad_host,
addr
));
}
}
client.set_base_url(addr);
} else if let Ok(addr) = kittycad_host_env {
client.set_base_url(addr);
}

View File

@ -81,7 +81,7 @@ fn unparse(test_name: &str) {
);
}
async fn execute(test_name: &str, render_to_png: bool) {
async fn execute(test_name: &str) {
// Read the AST from disk.
let input = read("ast.snap", test_name);
let ast_res: Result<Node<Program>, KclError> = get(&input);
@ -93,9 +93,7 @@ async fn execute(test_name: &str, render_to_png: bool) {
let exec_res = crate::test_server::execute_and_snapshot_ast(ast, crate::settings::types::UnitLength::Mm).await;
match exec_res {
Ok((program_memory, png)) => {
if render_to_png {
twenty_twenty::assert_image(format!("tests/{test_name}/rendered_model.png"), &png, 0.99);
}
twenty_twenty::assert_image(format!("tests/{test_name}/rendered_model.png"), &png, 0.99);
assert_snapshot(test_name, "Program memory after executing", || {
insta::assert_json_snapshot!("program_memory", program_memory);
});
@ -132,60 +130,6 @@ mod cube {
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}
mod helix_ccw {
const TEST_NAME: &str = "helix_ccw";
/// Test tokenizing KCL.
#[test]
fn tokenize() {
super::tokenize(TEST_NAME)
}
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[test]
fn unparse() {
super::unparse(TEST_NAME)
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}
mod double_map_fn {
const TEST_NAME: &str = "double_map_fn";
/// Test tokenizing KCL.
#[test]
fn tokenize() {
super::tokenize(TEST_NAME)
}
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[test]
fn unparse() {
super::unparse(TEST_NAME)
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, false).await
super::execute(TEST_NAME).await
}
}

View File

@ -0,0 +1,7 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 98
description: Error from executing cube.kcl
snapshot_kind: text
---
No API token found in environment variables. Use KITTYCAD_API_TOKEN or ZOO_API_TOKEN

View File

@ -1,234 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing double_map_fn.kcl
snapshot_kind: text
---
{
"Ok": {
"body": [
{
"declarations": [
{
"end": 40,
"id": {
"end": 12,
"name": "increment",
"start": 3,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"argument": {
"end": 38,
"left": {
"end": 34,
"name": "i",
"start": 33,
"type": "Identifier",
"type": "Identifier"
},
"operator": "+",
"right": {
"end": 38,
"raw": "1",
"start": 37,
"type": "Literal",
"type": "Literal",
"value": 1
},
"start": 33,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"end": 38,
"start": 26,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 40,
"start": 22
},
"end": 40,
"params": [
{
"type": "Parameter",
"identifier": {
"end": 17,
"name": "i",
"start": 16,
"type": "Identifier"
},
"optional": false
}
],
"start": 15,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"start": 3,
"type": "VariableDeclarator"
}
],
"end": 40,
"kind": "fn",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declarations": [
{
"end": 53,
"id": {
"end": 44,
"name": "xs",
"start": 42,
"type": "Identifier"
},
"init": {
"end": 53,
"endElement": {
"end": 52,
"raw": "2",
"start": 51,
"type": "Literal",
"type": "Literal",
"value": 2
},
"endInclusive": true,
"start": 47,
"startElement": {
"end": 49,
"raw": "0",
"start": 48,
"type": "Literal",
"type": "Literal",
"value": 0
},
"type": "ArrayRangeExpression",
"type": "ArrayRangeExpression"
},
"start": 42,
"type": "VariableDeclarator"
}
],
"end": 53,
"kind": "const",
"start": 42,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declarations": [
{
"end": 107,
"id": {
"end": 56,
"name": "ys",
"start": 54,
"type": "Identifier"
},
"init": {
"body": [
{
"end": 61,
"name": "xs",
"start": 59,
"type": "Identifier",
"type": "Identifier"
},
{
"arguments": [
{
"end": 72,
"start": 71,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
},
{
"end": 83,
"name": "increment",
"start": 74,
"type": "Identifier",
"type": "Identifier"
}
],
"callee": {
"end": 70,
"name": "map",
"start": 67,
"type": "Identifier"
},
"end": 84,
"optional": false,
"start": 67,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 95,
"start": 94,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
},
{
"end": 106,
"name": "increment",
"start": 97,
"type": "Identifier",
"type": "Identifier"
}
],
"callee": {
"end": 93,
"name": "map",
"start": 90,
"type": "Identifier"
},
"end": 107,
"optional": false,
"start": 90,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 107,
"start": 59,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 54,
"type": "VariableDeclarator"
}
],
"end": 107,
"kind": "const",
"start": 54,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 108,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 42,
"start": 40,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -1,8 +0,0 @@
fn increment = (i) => {
return i + 1
}
xs = [0..2]
ys = xs
|> map(%, increment)
|> map(%, increment)

View File

@ -1,171 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Program memory after executing double_map_fn.kcl
snapshot_kind: text
---
{
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"__meta": []
},
"increment": {
"type": "Function",
"expression": {
"body": {
"body": [
{
"argument": {
"end": 38,
"left": {
"end": 34,
"name": "i",
"start": 33,
"type": "Identifier",
"type": "Identifier"
},
"operator": "+",
"right": {
"end": 38,
"raw": "1",
"start": 37,
"type": "Literal",
"type": "Literal",
"value": 1
},
"start": 33,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"end": 38,
"start": 26,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 40,
"start": 22
},
"end": 40,
"params": [
{
"type": "Parameter",
"identifier": {
"end": 17,
"name": "i",
"start": 16,
"type": "Identifier"
},
"optional": false
}
],
"start": 15,
"type": "FunctionExpression"
},
"memory": {
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"__meta": []
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
},
"__meta": [
{
"sourceRange": [
15,
40
]
}
]
},
"xs": {
"type": "UserVal",
"type": "UserVal",
"value": [
0,
1,
2
],
"__meta": [
{
"sourceRange": [
47,
53
]
}
]
},
"ys": {
"type": "UserVal",
"type": "UserVal",
"value": [
2.0,
3.0,
4.0
],
"__meta": [
{
"sourceRange": [
90,
107
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

View File

@ -1,369 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Result of tokenizing double_map_fn.kcl
snapshot_kind: text
---
{
"Ok": [
{
"type": "keyword",
"start": 0,
"end": 2,
"value": "fn"
},
{
"type": "whitespace",
"start": 2,
"end": 3,
"value": " "
},
{
"type": "word",
"start": 3,
"end": 12,
"value": "increment"
},
{
"type": "whitespace",
"start": 12,
"end": 13,
"value": " "
},
{
"type": "operator",
"start": 13,
"end": 14,
"value": "="
},
{
"type": "whitespace",
"start": 14,
"end": 15,
"value": " "
},
{
"type": "brace",
"start": 15,
"end": 16,
"value": "("
},
{
"type": "word",
"start": 16,
"end": 17,
"value": "i"
},
{
"type": "brace",
"start": 17,
"end": 18,
"value": ")"
},
{
"type": "whitespace",
"start": 18,
"end": 19,
"value": " "
},
{
"type": "operator",
"start": 19,
"end": 21,
"value": "=>"
},
{
"type": "whitespace",
"start": 21,
"end": 22,
"value": " "
},
{
"type": "brace",
"start": 22,
"end": 23,
"value": "{"
},
{
"type": "whitespace",
"start": 23,
"end": 26,
"value": "\n "
},
{
"type": "keyword",
"start": 26,
"end": 32,
"value": "return"
},
{
"type": "whitespace",
"start": 32,
"end": 33,
"value": " "
},
{
"type": "word",
"start": 33,
"end": 34,
"value": "i"
},
{
"type": "whitespace",
"start": 34,
"end": 35,
"value": " "
},
{
"type": "operator",
"start": 35,
"end": 36,
"value": "+"
},
{
"type": "whitespace",
"start": 36,
"end": 37,
"value": " "
},
{
"type": "number",
"start": 37,
"end": 38,
"value": "1"
},
{
"type": "whitespace",
"start": 38,
"end": 39,
"value": "\n"
},
{
"type": "brace",
"start": 39,
"end": 40,
"value": "}"
},
{
"type": "whitespace",
"start": 40,
"end": 42,
"value": "\n\n"
},
{
"type": "word",
"start": 42,
"end": 44,
"value": "xs"
},
{
"type": "whitespace",
"start": 44,
"end": 45,
"value": " "
},
{
"type": "operator",
"start": 45,
"end": 46,
"value": "="
},
{
"type": "whitespace",
"start": 46,
"end": 47,
"value": " "
},
{
"type": "brace",
"start": 47,
"end": 48,
"value": "["
},
{
"type": "number",
"start": 48,
"end": 49,
"value": "0"
},
{
"type": "doublePeriod",
"start": 49,
"end": 51,
"value": ".."
},
{
"type": "number",
"start": 51,
"end": 52,
"value": "2"
},
{
"type": "brace",
"start": 52,
"end": 53,
"value": "]"
},
{
"type": "whitespace",
"start": 53,
"end": 54,
"value": "\n"
},
{
"type": "word",
"start": 54,
"end": 56,
"value": "ys"
},
{
"type": "whitespace",
"start": 56,
"end": 57,
"value": " "
},
{
"type": "operator",
"start": 57,
"end": 58,
"value": "="
},
{
"type": "whitespace",
"start": 58,
"end": 59,
"value": " "
},
{
"type": "word",
"start": 59,
"end": 61,
"value": "xs"
},
{
"type": "whitespace",
"start": 61,
"end": 64,
"value": "\n "
},
{
"type": "operator",
"start": 64,
"end": 66,
"value": "|>"
},
{
"type": "whitespace",
"start": 66,
"end": 67,
"value": " "
},
{
"type": "word",
"start": 67,
"end": 70,
"value": "map"
},
{
"type": "brace",
"start": 70,
"end": 71,
"value": "("
},
{
"type": "operator",
"start": 71,
"end": 72,
"value": "%"
},
{
"type": "comma",
"start": 72,
"end": 73,
"value": ","
},
{
"type": "whitespace",
"start": 73,
"end": 74,
"value": " "
},
{
"type": "word",
"start": 74,
"end": 83,
"value": "increment"
},
{
"type": "brace",
"start": 83,
"end": 84,
"value": ")"
},
{
"type": "whitespace",
"start": 84,
"end": 87,
"value": "\n "
},
{
"type": "operator",
"start": 87,
"end": 89,
"value": "|>"
},
{
"type": "whitespace",
"start": 89,
"end": 90,
"value": " "
},
{
"type": "word",
"start": 90,
"end": 93,
"value": "map"
},
{
"type": "brace",
"start": 93,
"end": 94,
"value": "("
},
{
"type": "operator",
"start": 94,
"end": 95,
"value": "%"
},
{
"type": "comma",
"start": 95,
"end": 96,
"value": ","
},
{
"type": "whitespace",
"start": 96,
"end": 97,
"value": " "
},
{
"type": "word",
"start": 97,
"end": 106,
"value": "increment"
},
{
"type": "brace",
"start": 106,
"end": 107,
"value": ")"
},
{
"type": "whitespace",
"start": 107,
"end": 108,
"value": "\n"
}
]
}

View File

@ -1,262 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing helix_ccw.kcl
snapshot_kind: text
---
{
"Ok": {
"body": [
{
"declarations": [
{
"end": 183,
"id": {
"end": 7,
"name": "part001",
"start": 0,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"end": 28,
"raw": "'XY'",
"start": 24,
"type": "Literal",
"type": "Literal",
"value": "XY"
}
],
"callee": {
"end": 23,
"name": "startSketchOn",
"start": 10,
"type": "Identifier"
},
"end": 29,
"optional": false,
"start": 10,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 72,
"properties": [
{
"end": 58,
"key": {
"end": 50,
"name": "center",
"start": 44,
"type": "Identifier"
},
"start": 44,
"type": "ObjectProperty",
"value": {
"elements": [
{
"end": 54,
"raw": "5",
"start": 53,
"type": "Literal",
"type": "Literal",
"value": 5
},
{
"end": 57,
"raw": "5",
"start": 56,
"type": "Literal",
"type": "Literal",
"value": 5
}
],
"end": 58,
"start": 52,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
},
{
"end": 70,
"key": {
"end": 66,
"name": "radius",
"start": 60,
"type": "Identifier"
},
"start": 60,
"type": "ObjectProperty",
"value": {
"end": 70,
"raw": "10",
"start": 68,
"type": "Literal",
"type": "Literal",
"value": 10
}
}
],
"start": 42,
"type": "ObjectExpression",
"type": "ObjectExpression"
},
{
"end": 75,
"start": 74,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 41,
"name": "circle",
"start": 35,
"type": "Identifier"
},
"end": 76,
"optional": false,
"start": 35,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 92,
"raw": "10",
"start": 90,
"type": "Literal",
"type": "Literal",
"value": 10
},
{
"end": 95,
"start": 94,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 89,
"name": "extrude",
"start": 82,
"type": "Identifier"
},
"end": 96,
"optional": false,
"start": 82,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 179,
"properties": [
{
"end": 132,
"key": {
"end": 128,
"name": "revolutions",
"start": 117,
"type": "Identifier"
},
"start": 117,
"type": "ObjectProperty",
"value": {
"end": 132,
"raw": "16",
"start": 130,
"type": "Literal",
"type": "Literal",
"value": 16
}
},
{
"end": 154,
"key": {
"end": 151,
"name": "angleStart",
"start": 141,
"type": "Identifier"
},
"start": 141,
"type": "ObjectProperty",
"value": {
"end": 154,
"raw": "0",
"start": 153,
"type": "Literal",
"type": "Literal",
"value": 0
}
},
{
"end": 172,
"key": {
"end": 166,
"name": "ccw",
"start": 163,
"type": "Identifier"
},
"start": 163,
"type": "ObjectProperty",
"value": {
"end": 172,
"raw": "true",
"start": 168,
"type": "Literal",
"type": "Literal",
"value": true
}
}
],
"start": 108,
"type": "ObjectExpression",
"type": "ObjectExpression"
},
{
"end": 182,
"start": 181,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 107,
"name": "helix",
"start": 102,
"type": "Identifier"
},
"end": 183,
"optional": false,
"start": 102,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 183,
"start": 10,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 0,
"type": "VariableDeclarator"
}
],
"end": 183,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 184,
"start": 0
}
}

View File

@ -1,8 +0,0 @@
part001 = startSketchOn('XY')
|> circle({ center: [5, 5], radius: 10 }, %)
|> extrude(10, %)
|> helix({
revolutions: 16,
angleStart: 0,
ccw: true
}, %)

View File

@ -1,151 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Program memory after executing helix_ccw.kcl
snapshot_kind: text
---
{
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"__meta": []
},
"part001": {
"type": "Solid",
"type": "Solid",
"id": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
35,
76
],
"tag": null,
"type": "extrudeArc"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
35,
76
]
},
"ccw": true,
"center": [
5.0,
5.0
],
"from": [
5.0,
5.0
],
"radius": 10.0,
"tag": null,
"to": [
5.0,
5.0
],
"type": "Circle"
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"__meta": []
},
"start": {
"from": [
15.0,
5.0
],
"to": [
15.0,
5.0
],
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
35,
76
]
}
},
"__meta": [
{
"sourceRange": [
35,
76
]
}
]
},
"height": 10.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"__meta": [
{
"sourceRange": [
35,
76
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

View File

@ -1,465 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Result of tokenizing helix_ccw.kcl
snapshot_kind: text
---
{
"Ok": [
{
"type": "word",
"start": 0,
"end": 7,
"value": "part001"
},
{
"type": "whitespace",
"start": 7,
"end": 8,
"value": " "
},
{
"type": "operator",
"start": 8,
"end": 9,
"value": "="
},
{
"type": "whitespace",
"start": 9,
"end": 10,
"value": " "
},
{
"type": "word",
"start": 10,
"end": 23,
"value": "startSketchOn"
},
{
"type": "brace",
"start": 23,
"end": 24,
"value": "("
},
{
"type": "string",
"start": 24,
"end": 28,
"value": "'XY'"
},
{
"type": "brace",
"start": 28,
"end": 29,
"value": ")"
},
{
"type": "whitespace",
"start": 29,
"end": 32,
"value": "\n "
},
{
"type": "operator",
"start": 32,
"end": 34,
"value": "|>"
},
{
"type": "whitespace",
"start": 34,
"end": 35,
"value": " "
},
{
"type": "word",
"start": 35,
"end": 41,
"value": "circle"
},
{
"type": "brace",
"start": 41,
"end": 42,
"value": "("
},
{
"type": "brace",
"start": 42,
"end": 43,
"value": "{"
},
{
"type": "whitespace",
"start": 43,
"end": 44,
"value": " "
},
{
"type": "word",
"start": 44,
"end": 50,
"value": "center"
},
{
"type": "colon",
"start": 50,
"end": 51,
"value": ":"
},
{
"type": "whitespace",
"start": 51,
"end": 52,
"value": " "
},
{
"type": "brace",
"start": 52,
"end": 53,
"value": "["
},
{
"type": "number",
"start": 53,
"end": 54,
"value": "5"
},
{
"type": "comma",
"start": 54,
"end": 55,
"value": ","
},
{
"type": "whitespace",
"start": 55,
"end": 56,
"value": " "
},
{
"type": "number",
"start": 56,
"end": 57,
"value": "5"
},
{
"type": "brace",
"start": 57,
"end": 58,
"value": "]"
},
{
"type": "comma",
"start": 58,
"end": 59,
"value": ","
},
{
"type": "whitespace",
"start": 59,
"end": 60,
"value": " "
},
{
"type": "word",
"start": 60,
"end": 66,
"value": "radius"
},
{
"type": "colon",
"start": 66,
"end": 67,
"value": ":"
},
{
"type": "whitespace",
"start": 67,
"end": 68,
"value": " "
},
{
"type": "number",
"start": 68,
"end": 70,
"value": "10"
},
{
"type": "whitespace",
"start": 70,
"end": 71,
"value": " "
},
{
"type": "brace",
"start": 71,
"end": 72,
"value": "}"
},
{
"type": "comma",
"start": 72,
"end": 73,
"value": ","
},
{
"type": "whitespace",
"start": 73,
"end": 74,
"value": " "
},
{
"type": "operator",
"start": 74,
"end": 75,
"value": "%"
},
{
"type": "brace",
"start": 75,
"end": 76,
"value": ")"
},
{
"type": "whitespace",
"start": 76,
"end": 79,
"value": "\n "
},
{
"type": "operator",
"start": 79,
"end": 81,
"value": "|>"
},
{
"type": "whitespace",
"start": 81,
"end": 82,
"value": " "
},
{
"type": "word",
"start": 82,
"end": 89,
"value": "extrude"
},
{
"type": "brace",
"start": 89,
"end": 90,
"value": "("
},
{
"type": "number",
"start": 90,
"end": 92,
"value": "10"
},
{
"type": "comma",
"start": 92,
"end": 93,
"value": ","
},
{
"type": "whitespace",
"start": 93,
"end": 94,
"value": " "
},
{
"type": "operator",
"start": 94,
"end": 95,
"value": "%"
},
{
"type": "brace",
"start": 95,
"end": 96,
"value": ")"
},
{
"type": "whitespace",
"start": 96,
"end": 99,
"value": "\n "
},
{
"type": "operator",
"start": 99,
"end": 101,
"value": "|>"
},
{
"type": "whitespace",
"start": 101,
"end": 102,
"value": " "
},
{
"type": "word",
"start": 102,
"end": 107,
"value": "helix"
},
{
"type": "brace",
"start": 107,
"end": 108,
"value": "("
},
{
"type": "brace",
"start": 108,
"end": 109,
"value": "{"
},
{
"type": "whitespace",
"start": 109,
"end": 117,
"value": "\n "
},
{
"type": "word",
"start": 117,
"end": 128,
"value": "revolutions"
},
{
"type": "colon",
"start": 128,
"end": 129,
"value": ":"
},
{
"type": "whitespace",
"start": 129,
"end": 130,
"value": " "
},
{
"type": "number",
"start": 130,
"end": 132,
"value": "16"
},
{
"type": "comma",
"start": 132,
"end": 133,
"value": ","
},
{
"type": "whitespace",
"start": 133,
"end": 141,
"value": "\n "
},
{
"type": "word",
"start": 141,
"end": 151,
"value": "angleStart"
},
{
"type": "colon",
"start": 151,
"end": 152,
"value": ":"
},
{
"type": "whitespace",
"start": 152,
"end": 153,
"value": " "
},
{
"type": "number",
"start": 153,
"end": 154,
"value": "0"
},
{
"type": "comma",
"start": 154,
"end": 155,
"value": ","
},
{
"type": "whitespace",
"start": 155,
"end": 163,
"value": "\n "
},
{
"type": "word",
"start": 163,
"end": 166,
"value": "ccw"
},
{
"type": "colon",
"start": 166,
"end": 167,
"value": ":"
},
{
"type": "whitespace",
"start": 167,
"end": 168,
"value": " "
},
{
"type": "keyword",
"start": 168,
"end": 172,
"value": "true"
},
{
"type": "whitespace",
"start": 172,
"end": 178,
"value": "\n "
},
{
"type": "brace",
"start": 178,
"end": 179,
"value": "}"
},
{
"type": "comma",
"start": 179,
"end": 180,
"value": ","
},
{
"type": "whitespace",
"start": 180,
"end": 181,
"value": " "
},
{
"type": "operator",
"start": 181,
"end": 182,
"value": "%"
},
{
"type": "brace",
"start": 182,
"end": 183,
"value": ")"
},
{
"type": "whitespace",
"start": 183,
"end": 184,
"value": "\n"
}
]
}

View File

@ -22,6 +22,6 @@ mod TEST_NAME_HERE {
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, RENDER_TO_PNG).await
super::execute(TEST_NAME).await
}
}

View File

@ -0,0 +1,4 @@
const part001 = startSketchOn('XY')
|> circle({ center: [5, 5], radius: 10 }, %)
|> extrude(10, %)
|> helix({revolutions: 16, angle_start: 0, ccw: true}, %)

View File

@ -0,0 +1,6 @@
fn increment = (i) => { return i + 1 }
xs = [0..2]
ys = xs
|> map(%, increment)
|> map(%, increment)

View File

@ -153,6 +153,14 @@ async fn kcl_test_helix_defaults_negative_extrude() {
assert_out("helix_defaults_negative_extrude", &result);
}
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_helix_ccw() {
let code = kcl_input!("helix_ccw");
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
assert_out("helix_ccw", &result);
}
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_helix_with_length() {
let code = kcl_input!("helix_with_length");

View File

@ -172,6 +172,7 @@ gen_test_parse_fail!(
// "syntax: Can import only import at the top level"
// );
gen_test!(add_lots);
gen_test!(double_map);
gen_test!(array_elem_push);
gen_test_fail!(
array_elem_push_fail,