Merge branch 'franknoirot/4088/decouple-homeMachine' into franknoirot/4088/create-file-url

This commit is contained in:
Frank Noirot
2024-10-10 15:36:32 -04:00
82 changed files with 1175 additions and 613 deletions

View File

@ -57,7 +57,7 @@ yarn install
followed by: followed by:
``` ```
yarn build:wasm-dev yarn build:wasm
``` ```
or if you have the gh cli installed or if you have the gh cli installed
@ -66,15 +66,15 @@ or if you have the gh cli installed
./get-latest-wasm-bundle.sh # this will download the latest main wasm bundle ./get-latest-wasm-bundle.sh # this will download the latest main wasm bundle
``` ```
That will build the WASM binary and put in the `public` dir (though gitignored) That will build the WASM binary and put in the `public` dir (though gitignored).
finally, to run the web app only, run: Finally, to run the web app only, run:
``` ```
yarn start yarn start
``` ```
If you're not an KittyCAD employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens ofcourse, then navigate to localhost:3000 again. Note that navigating to localhost:3000/signin removes your token so you will need to set the token again. If you're not an KittyCAD employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens of course, then navigate to localhost:3000 again. Note that navigating to `localhost:3000/signin` removes your token so you will need to set the token again.
### Development environment variables ### Development environment variables
@ -91,13 +91,13 @@ Third-Party Cookies".
## Desktop ## Desktop
To spin up the desktop app, `yarn install` and `yarn build:wasm-dev` need to have been done before hand then To spin up the desktop app, `yarn install` and `yarn build:wasm` need to have been done before hand then
``` ```
yarn electron:start yarn tron:start
``` ```
This will start the application and hot-reload on changed. This will start the application and hot-reload on changes.
Devtools can be opened with the usual Cmd/Ctrl-Shift-I. Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
@ -128,7 +128,18 @@ Before you submit a contribution PR to this repo, please ensure that:
## Release a new version ## Release a new version
#### 1. Bump the versions by running `./make-release.sh` and create a Cut Release PR #### 1. Bump the versions by running `./make-release.sh`
The `./make-release.sh` script has git commands to pull main but to be sure you can run the following git commands to have a fresh `main` locally.
```
git branch -D main
git checkout main
git pull origin
./make-release.sh
# Copy within the back ticks and paste the stdout of the change log
git push --set-upstream origin <branch name created from ./make-release.sh>
```
That will create the branch with the updated json files for you: That will create the branch with the updated json files for you:
- run `./make-release.sh` or `./make-release.sh patch` for a patch update; - run `./make-release.sh` or `./make-release.sh patch` for a patch update;
@ -137,28 +148,32 @@ That will create the branch with the updated json files for you:
After it runs you should just need the push the branch and open a PR. After it runs you should just need the push the branch and open a PR.
**Important:** It needs to be prefixed with `Cut release v` to build in release mode and a few other things to test in the best context possible, the intent would be for instance to have `Cut release v1.2.3` for the `v1.2.3` release candidate. #### 2. Create a Cut Release PR
When you open the PR copy the change log from the output of the `./make-release.sh` script into the description of the PR.
**Important:** Pull request title needs to be prefixed with `Cut release v` to build in release mode and a few other things to test in the best context possible, the intent would be for instance to have `Cut release v1.2.3` for the `v1.2.3` release candidate.
The PR may then serve as a place to discuss the human-readable changelog and extra QA. The `make-release.sh` tool suggests a changelog for you too to be used as PR description, just make sure to delete lines that are not user facing. The PR may then serve as a place to discuss the human-readable changelog and extra QA. The `make-release.sh` tool suggests a changelog for you too to be used as PR description, just make sure to delete lines that are not user facing.
#### 2. Smoke test artifacts from the Cut Release PR #### 3. Manually test artifacts from the Cut Release PR
The release builds can be find under the `artifact` zip, at the very bottom of the `ci` action page for each commit on this branch. The release builds can be find under the `artifact` zip, at the very bottom of the `ci` action page for each commit on this branch.
We don't have a strict process, but click around and check for anything obvious, posting results as comments in the Cut Release PR. Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the Cut Release PR.
The other `ci` output in Cut Release PRs is `updater-test`, because we don't have a way to test this fully automated, we have a semi-automated process. Download updater-test zip file, install the app, run it, expect an updater prompt to a dummy v0.99.99, install it and check that the app comes back at that version (on both macOS and Windows). The other `ci` output in Cut Release PRs is `updater-test`, because we don't have a way to test this fully automated, we have a semi-automated process. Download updater-test zip file, install the app, run it, expect an updater prompt to a dummy v0.99.99, install it and check that the app comes back at that version (on both macOS and Windows).
#### 3. Merge the Cut Release PR #### 4. Merge the Cut Release PR
This will kick the `create-release` action, that creates a _Draft_ release out of this Cut Release PR merge after less than a minute, with the new version as title and Cut Release PR as description. This will kick the `create-release` action, that creates a _Draft_ release out of this Cut Release PR merge after less than a minute, with the new version as title and Cut Release PR as description.
#### 4. Publish the release #### 5. Publish the release
Head over to https://github.com/KittyCAD/modeling-app/releases, the draft release corresponding to the merged Cut Release PR should show up at the top as _Draft_. Click on it, verify the content, and hit _Publish_. Head over to https://github.com/KittyCAD/modeling-app/releases, the draft release corresponding to the merged Cut Release PR should show up at the top as _Draft_. Click on it, verify the content, and hit _Publish_.
#### 5. Profit #### 6. Profit
A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter. A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter.
@ -319,7 +334,16 @@ Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testin
```bash ```bash
cd src/wasm-lib cd src/wasm-lib
cargo test KITTYCAD_API_TOKEN=XXX cargo test -- --test-threads=1
```
Where `XXX` is an API token from the production engine (NOT the dev environment).
We recommend using [nextest](https://nexte.st/) to run the Rust tests (its faster and is used in CI). Once installed, run the tests using
```
cd src/wasm-lib
KITTYCAD_API_TOKEN=XXX cargo run nextest
``` ```
### Mapping CI CD jobs to local commands ### Mapping CI CD jobs to local commands

View File

@ -18,12 +18,12 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes | | `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
| `start` | [`KclValue`](/docs/kcl/types/KclValue) | A memory item. | Yes | | `start` | [`KclValue`](/docs/kcl/types/KclValue) | Any KCL value. | Yes |
| `reduce_fn` | `FunctionParam` | | Yes | | `reduce_fn` | `FunctionParam` | | Yes |
### Returns ### Returns
[`KclValue`](/docs/kcl/types/KclValue) - A memory item. [`KclValue`](/docs/kcl/types/KclValue) - Any KCL value.
### Examples ### Examples

View File

@ -80912,7 +80912,7 @@
}, },
"definitions": { "definitions": {
"KclValue": { "KclValue": {
"description": "A memory item.", "description": "Any KCL value.",
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",
@ -84468,7 +84468,7 @@
"type": "null", "type": "null",
"definitions": { "definitions": {
"KclValue": { "KclValue": {
"description": "A memory item.", "description": "Any KCL value.",
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",
@ -88028,7 +88028,7 @@
}, },
"definitions": { "definitions": {
"KclValue": { "KclValue": {
"description": "A memory item.", "description": "Any KCL value.",
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",
@ -112440,7 +112440,7 @@
}, },
"definitions": { "definitions": {
"KclValue": { "KclValue": {
"description": "A memory item.", "description": "Any KCL value.",
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",
@ -115993,7 +115993,7 @@
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "KclValue", "title": "KclValue",
"description": "A memory item.", "description": "Any KCL value.",
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",
@ -116389,7 +116389,7 @@
], ],
"definitions": { "definitions": {
"KclValue": { "KclValue": {
"description": "A memory item.", "description": "Any KCL value.",
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",
@ -119945,7 +119945,7 @@
"type": "null", "type": "null",
"definitions": { "definitions": {
"KclValue": { "KclValue": {
"description": "A memory item.", "description": "Any KCL value.",
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",
@ -123499,7 +123499,7 @@
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "KclValue", "title": "KclValue",
"description": "A memory item.", "description": "Any KCL value.",
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",
@ -127037,7 +127037,7 @@
} }
}, },
"KclValue": { "KclValue": {
"description": "A memory item.", "description": "Any KCL value.",
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",

View File

@ -1,10 +1,10 @@
--- ---
title: "KclValue" title: "KclValue"
excerpt: "A memory item." excerpt: "Any KCL value."
layout: manual layout: manual
--- ---
A memory item. Any KCL value.
@ -80,7 +80,7 @@ A plane.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Plane`| | No | | `type` |enum: `Plane`| | No |
| `id` |`string`| The id of the plane. | No | | `id` |`string`| The id of the plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A memory item. | No | | `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No | | `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No | | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
@ -183,8 +183,8 @@ Data for an imported geometry.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Function`| | No | | `type` |enum: `Function`| | No |
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| A memory item. | No | | `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| A memory item. | No | | `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -0,0 +1,80 @@
import { test, expect } from '@playwright/test'
import { getUtils, setup, tearDown } from './test-utils'
test.beforeEach(async ({ context, page }, testInfo) => {
await setup(context, page, testInfo)
})
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
function countNewlines(input: string): number {
let count = 0
for (const char of input) {
if (char === '\n') {
count++
}
}
return count
}
test.describe('Debug pane', () => {
test('Artifact IDs in the artifact graph are stable across code edits', async ({
page,
context,
}) => {
const code = `sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([1, 1], %)
`
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const tree = page.getByTestId('debug-feature-tree')
const segment = tree.locator('li', {
hasText: 'segIds:',
hasNotText: 'paths:',
})
await test.step('Test setup', async () => {
await u.waitForAuthSkipAppStart()
await u.openKclCodePanel()
await u.openDebugPanel()
// Set the code in the code editor.
await u.codeLocator.click()
await page.keyboard.type(code, { delay: 0 })
// Scroll to the feature tree.
await tree.scrollIntoViewIfNeeded()
// Expand the feature tree.
await tree.getByText('Feature Tree').click()
// Just expanded the details, making the element taller, so scroll again.
await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
})
// Extract the artifact IDs from the debug feature tree.
const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
// The artifact ID should include a UUID.
expect(initialSegmentIds).toMatch(
/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/
)
await test.step('Move cursor to the bottom of the code editor', async () => {
// Focus on the code editor.
await u.codeLocator.click()
// Make sure the cursor is at the end of the code.
const lines = countNewlines(code) + 1
for (let i = 0; i < lines; i++) {
await page.keyboard.press('ArrowDown')
}
})
await test.step('Enter a comment', async () => {
await page.keyboard.type('|> line([2, 2], %)', { delay: 0 })
// Wait for keyboard input debounce and updated artifact graph.
await page.waitForTimeout(1000)
})
const newSegmentIds = await segment.innerText()
// Strip off the closing bracket.
const initialIds = initialSegmentIds.slice(0, initialSegmentIds.length - 1)
expect(newSegmentIds.slice(0, initialIds.length)).toEqual(initialIds)
})
})

View File

@ -856,23 +856,30 @@ test(
'Deleting projects, can delete individual project, can still create projects after deleting all', 'Deleting projects, can delete individual project, can still create projects after deleting all',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ browserName }, testInfo) => {
const projectData = [
['router-template-slate', 'cylinder.kcl'],
['bracket', 'focusrite_scarlett_mounting_braket.kcl'],
['lego', 'lego.kcl'],
]
const { electronApp, page } = await setupElectron({ const { electronApp, page } = await setupElectron({
testInfo, testInfo,
folderSetupFn: async (dir) => {
// Do these serially to ensure the order is correct
for (const [name, file] of projectData) {
await fsp.mkdir(join(dir, name), { recursive: true })
await fsp.copyFile(
executorInputPath(file),
join(dir, name, `main.kcl`)
)
// Wait 1s between each project to ensure the order is correct
await new Promise((r) => setTimeout(r, 1_000))
}
},
}) })
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log) page.on('console', console.log)
// we need to create the folders so that the order is correct
// creating them ahead of time with fs tools means they all have the same timestamp
await createProject({
name: 'router-template-slate',
page,
returnHome: true,
})
await createProject({ name: 'bracket', page, returnHome: true })
await createProject({ name: 'lego', page, returnHome: true })
await test.step('delete the middle project, i.e. the bracket project', async () => { await test.step('delete the middle project, i.e. the bracket project', async () => {
const project = page.getByTestId('project-link').getByText('bracket') const project = page.getByTestId('project-link').getByText('bracket')
@ -972,8 +979,26 @@ test(
'Can sort projects on home page', 'Can sort projects on home page',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ browserName }, testInfo) => {
const projectData = [
['router-template-slate', 'cylinder.kcl'],
['bracket', 'focusrite_scarlett_mounting_braket.kcl'],
['lego', 'lego.kcl'],
]
const { electronApp, page } = await setupElectron({ const { electronApp, page } = await setupElectron({
testInfo, testInfo,
folderSetupFn: async (dir) => {
// Do these serially to ensure the order is correct
for (const [name, file] of projectData) {
await fsp.mkdir(join(dir, name), { recursive: true })
await fsp.copyFile(
executorInputPath(file),
join(dir, name, `main.kcl`)
)
// Wait 1s between each project to ensure the order is correct
await new Promise((r) => setTimeout(r, 1_000))
}
},
}) })
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
@ -981,16 +1006,6 @@ test(
page.on('console', console.log) page.on('console', console.log)
// we need to create the folders so that the order is correct
// creating them ahead of time with fs tools means they all have the same timestamp
await createProject({
name: 'router-template-slate',
page,
returnHome: true,
})
await createProject({ name: 'bracket', page, returnHome: true })
await createProject({ name: 'lego', page, returnHome: true })
await test.step('should be shorted by modified initially', async () => { await test.step('should be shorted by modified initially', async () => {
const lastModifiedButton = page.getByRole('button', { const lastModifiedButton = page.getByRole('button', {
name: 'Last Modified', name: 'Last Modified',
@ -1086,7 +1101,9 @@ test(
await expect(page.getByText('No Projects found')).toBeVisible() await expect(page.getByText('No Projects found')).toBeVisible()
await createProject({ name: 'project-000', page, returnHome: true }) await createProject({ name: 'project-000', page, returnHome: true })
await expect(page.getByText('project-000')).toBeVisible() await expect(
page.getByTestId('project-link').getByText('project-000')
).toBeVisible()
await page.getByTestId('project-link').getByText('project-000').click() await page.getByTestId('project-link').getByText('project-000').click()

View File

@ -521,7 +521,6 @@ test(
const startXPx = 600 const startXPx = 600
// Equip the rectangle tool // Equip the rectangle tool
await page.getByRole('button', { name: 'line Line', exact: true }).click()
await page await page
.getByRole('button', { name: 'rectangle Corner rectangle', exact: true }) .getByRole('button', { name: 'rectangle Corner rectangle', exact: true })
.click() .click()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1208,6 +1208,12 @@ extrude001 = extrude(50, sketch001)
test('Deselecting line tool should mean nothing happens on click', async ({ test('Deselecting line tool should mean nothing happens on click', async ({
page, page,
}) => { }) => {
/**
* If the line tool is clicked when the state is 'No Points' it will exit Sketch mode.
* This is the same exact workflow as pressing ESC.
*
* To continue to test this workflow, we now enter sketch mode and place a single point before exiting the line tool.
*/
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
@ -1228,6 +1234,7 @@ extrude001 = extrude(50, sketch001)
200 200
) )
// Clicks the XZ Plane in the page
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
@ -1236,6 +1243,11 @@ extrude001 = extrude(50, sketch001)
await page.waitForTimeout(600) await page.waitForTimeout(600)
// Place a point because the line tool will exit if no points are pressed
await page.mouse.click(650, 200)
await page.waitForTimeout(600)
// Code before exiting the tool
let previousCodeContent = await page.locator('.cm-content').innerText() let previousCodeContent = await page.locator('.cm-content').innerText()
// deselect the line tool by clicking it // deselect the line tool by clicking it

4
interface.d.ts vendored
View File

@ -69,9 +69,13 @@ export interface IElectronAPI {
kittycad: (access: string, args: any) => any kittycad: (access: string, args: any) => any
listMachines: () => Promise<MachinesListing> listMachines: () => Promise<MachinesListing>
getMachineApiIp: () => Promise<string | null> getMachineApiIp: () => Promise<string | null>
onUpdateDownloadStart: (
callback: (value: { version: string }) => void
) => Electron.IpcRenderer
onUpdateDownloaded: ( onUpdateDownloaded: (
callback: (value: string) => void callback: (value: string) => void
) => Electron.IpcRenderer ) => Electron.IpcRenderer
onUpdateError: (callback: (value: { error: Error }) => void) => Electron
appRestart: () => void appRestart: () => void
} }

View File

@ -408,6 +408,7 @@ export async function deleteSegment({
const testExecute = await executeAst({ const testExecute = await executeAst({
ast: modifiedAst, ast: modifiedAst,
idGenerator: kclManager.execState.idGenerator,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: engineCommandManager, engineCommandManager: engineCommandManager,
}) })

View File

@ -391,12 +391,14 @@ export class SceneEntities {
const { truncatedAst, programMemoryOverride, variableDeclarationName } = const { truncatedAst, programMemoryOverride, variableDeclarationName } =
prepared prepared
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory
const sketch = sketchFromPathToNode({ const sketch = sketchFromPathToNode({
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
ast: maybeModdedAst, ast: maybeModdedAst,
@ -801,12 +803,14 @@ export class SceneEntities {
updateRectangleSketch(sketchInit, x, y, tags[0]) updateRectangleSketch(sketchInit, x, y, tags[0])
} }
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName), programMemory.get(variableDeclarationName),
@ -848,12 +852,14 @@ export class SceneEntities {
await kclManager.executeAstMock(_ast) await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'Finish rectangle' }) sceneInfra.modelingSend({ type: 'Finish rectangle' })
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: _ast, ast: _ast,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory
// Prepare to update the THREEjs scene // Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
@ -965,12 +971,14 @@ export class SceneEntities {
modded = moddedResult.modifiedAst modded = moddedResult.modifiedAst
} }
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: modded, ast: modded,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName), programMemory.get(variableDeclarationName),
@ -1317,12 +1325,14 @@ export class SceneEntities {
// don't want to mod the user's code yet as they have't committed to the change yet // don't want to mod the user's code yet as they have't committed to the change yet
// plus this would be the truncated ast being recast, it would be wrong // plus this would be the truncated ast being recast, it would be wrong
codeManager.updateCodeEditor(code) codeManager.updateCodeEditor(code)
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
const maybeSketch = programMemory.get(variableDeclarationName) const maybeSketch = programMemory.get(variableDeclarationName)

View File

@ -157,7 +157,7 @@ export function useCalc({
engineCommandManager, engineCommandManager,
useFakeExecutor: true, useFakeExecutor: true,
programMemoryOverride: kclManager.programMemory.clone(), programMemoryOverride: kclManager.programMemory.clone(),
}).then(({ programMemory }) => { }).then(({ execState }) => {
const resultDeclaration = ast.body.find( const resultDeclaration = ast.body.find(
(a) => (a) =>
a.type === 'VariableDeclaration' && a.type === 'VariableDeclaration' &&
@ -166,7 +166,7 @@ export function useCalc({
const init = const init =
resultDeclaration?.type === 'VariableDeclaration' && resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.get('__result__')?.value const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN') setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init) init && setValueNode(init)
}) })

View File

@ -0,0 +1,111 @@
import { isArray, isNonNullable } from 'lib/utils'
import { useRef, useState } from 'react'
type Primitive = string | number | bigint | boolean | symbol | null | undefined
export type GenericObj = {
type?: string
[key: string]: GenericObj | Primitive | Array<GenericObj | Primitive>
}
/**
* Display an array of objects or primitives for debug purposes. Nullable values
* are displayed so that relative indexes are preserved.
*/
export function DebugDisplayArray({
arr,
filterKeys,
}: {
arr: Array<GenericObj | Primitive>
filterKeys: string[]
}) {
return (
<>
{arr.map((obj, index) => {
return (
<div className="my-2" key={index}>
{obj && typeof obj === 'object' ? (
<DebugDisplayObj obj={obj} filterKeys={filterKeys} />
) : isNonNullable(obj) ? (
<span>{obj.toString()}</span>
) : (
<span>{obj}</span>
)}
</div>
)
})}
</>
)
}
/**
* Display an object as a tree for debug purposes. Nullable values are omitted.
* The only other property treated specially is the type property, which is
* assumed to be a string.
*/
export function DebugDisplayObj({
obj,
filterKeys,
}: {
obj: GenericObj
filterKeys: string[]
}) {
const ref = useRef<HTMLPreElement>(null)
const hasCursor = false
const [isCollapsed, setIsCollapsed] = useState(false)
return (
<pre
ref={ref}
className={`ml-2 border-l border-violet-600 pl-1 ${
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
}`}
>
{isCollapsed ? (
<button
className="m-0 p-0 border-0"
onClick={() => setIsCollapsed(false)}
>
{'>'}type: {obj.type}
</button>
) : (
<span className="flex">
<button
className="m-0 p-0 border-0 mb-auto"
onClick={() => setIsCollapsed(true)}
>
{'⬇️'}
</button>
<ul className="inline-block">
{Object.entries(obj).map(([key, value]) => {
if (filterKeys.includes(key)) {
return null
} else if (isArray(value)) {
return (
<li key={key}>
{`${key}: [`}
<DebugDisplayArray arr={value} filterKeys={filterKeys} />
{']'}
</li>
)
} else if (typeof value === 'object' && value !== null) {
return (
<li key={key}>
{key}:
<DebugDisplayObj obj={value} filterKeys={filterKeys} />
</li>
)
} else if (isNonNullable(value)) {
return (
<li key={key}>
{key}: {value.toString()}
</li>
)
}
return null
})}
</ul>
</span>
)}
</pre>
)
}

View File

@ -0,0 +1,45 @@
import { useMemo } from 'react'
import { engineCommandManager } from 'lib/singletons'
import {
ArtifactGraph,
expandPlane,
PlaneArtifactRich,
} from 'lang/std/artifactGraph'
import { DebugDisplayArray, GenericObj } from './DebugDisplayObj'
export function DebugFeatureTree() {
const featureTree = useMemo(() => {
return computeTree(engineCommandManager.artifactGraph)
}, [engineCommandManager.artifactGraph])
const filterKeys: string[] = ['__meta', 'codeRef', 'pathToNode']
return (
<details data-testid="debug-feature-tree" className="relative">
<summary>Feature Tree</summary>
{featureTree.length > 0 ? (
<pre className="text-xs">
<DebugDisplayArray arr={featureTree} filterKeys={filterKeys} />
</pre>
) : (
<p>(Empty)</p>
)}
</details>
)
}
function computeTree(artifactGraph: ArtifactGraph): GenericObj[] {
let items: GenericObj[] = []
const planes: PlaneArtifactRich[] = []
for (const artifact of artifactGraph.values()) {
if (artifact.type === 'plane') {
planes.push(expandPlane(artifact, artifactGraph))
}
}
const extraRichPlanes: GenericObj[] = planes.map((plane) => {
return plane as any as GenericObj
})
items = items.concat(extraRichPlanes)
return items
}

View File

@ -149,6 +149,13 @@ export const ModelingMachineProvider = ({
}, },
'sketch exit execute': ({ context: { store } }) => { 'sketch exit execute': ({ context: { store } }) => {
;(async () => { ;(async () => {
// When cancelling the sketch mode we should disable sketch mode within the engine.
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'sketch_mode_disable' },
})
sceneInfra.camControls.syncDirection = 'clientToEngine' sceneInfra.camControls.syncDirection = 'clientToEngine'
if (cameraProjection.current === 'perspective') { if (cameraProjection.current === 'perspective') {

View File

@ -1,3 +1,4 @@
import { DebugFeatureTree } from 'components/DebugFeatureTree'
import { AstExplorer } from '../../AstExplorer' import { AstExplorer } from '../../AstExplorer'
import { EngineCommands } from '../../EngineCommands' import { EngineCommands } from '../../EngineCommands'
import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp' import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp'
@ -12,6 +13,7 @@ export const DebugPane = () => {
<EngineCommands /> <EngineCommands />
<CamDebugSettings /> <CamDebugSettings />
<AstExplorer /> <AstExplorer />
<DebugFeatureTree />
</div> </div>
</section> </section>
) )

View File

@ -29,8 +29,8 @@ describe('processMemory', () => {
|> lineTo([2.15, 4.32], %) |> lineTo([2.15, 4.32], %)
// |> rx(90, %)` // |> rx(90, %)`
const ast = parse(code) const ast = parse(code)
const programMemory = await enginelessExecutor(ast, ProgramMemory.empty()) const execState = await enginelessExecutor(ast, ProgramMemory.empty())
const output = processMemory(programMemory) const output = processMemory(execState.memory)
expect(output.myVar).toEqual(5) expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3) expect(output.otherVar).toEqual(3)
expect(output).toEqual({ expect(output).toEqual({

View File

@ -8,6 +8,7 @@ import ModalContainer from 'react-modal-promise'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { AppStreamProvider } from 'AppState' import { AppStreamProvider } from 'AppState'
import { ToastUpdate } from 'components/ToastUpdate' import { ToastUpdate } from 'components/ToastUpdate'
import { AUTO_UPDATER_TOAST_ID } from 'lib/constants'
// uncomment for xstate inspector // uncomment for xstate inspector
// import { DEV } from 'env' // import { DEV } from 'env'
@ -53,7 +54,22 @@ root.render(
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals() reportWebVitals()
isDesktop() && if (isDesktop()) {
// Listen for update download progress to begin
// to show a loading toast.
window.electron.onUpdateDownloadStart(() => {
const message = `Downloading app update...`
console.log(message)
toast.loading(message, { id: AUTO_UPDATER_TOAST_ID })
})
// Listen for update download errors to show
// an error toast and clear the loading toast.
window.electron.onUpdateError(({ error }) => {
console.error(error)
toast.error('An error occurred while downloading the update.', {
id: AUTO_UPDATER_TOAST_ID,
})
})
window.electron.onUpdateDownloaded((version: string) => { window.electron.onUpdateDownloaded((version: string) => {
const message = `A new update (${version}) was downloaded and will be available next time you open the app.` const message = `A new update (${version}) was downloaded and will be available next time you open the app.`
console.log(message) console.log(message)
@ -64,6 +80,7 @@ isDesktop() &&
window.electron.appRestart() window.electron.appRestart()
}, },
}), }),
{ duration: 30000 } { duration: 30000, id: AUTO_UPDATER_TOAST_ID }
) )
}) })
}

View File

@ -8,6 +8,8 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import { import {
CallExpression, CallExpression,
emptyExecState,
ExecState,
initPromise, initPromise,
parse, parse,
PathToNode, PathToNode,
@ -42,6 +44,7 @@ export class KclManager {
}, },
digest: null, digest: null,
} }
private _execState: ExecState = emptyExecState()
private _programMemory: ProgramMemory = ProgramMemory.empty() private _programMemory: ProgramMemory = ProgramMemory.empty()
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty() lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
private _logs: string[] = [] private _logs: string[] = []
@ -72,11 +75,21 @@ export class KclManager {
get programMemory() { get programMemory() {
return this._programMemory return this._programMemory
} }
set programMemory(programMemory) { // This is private because callers should be setting the entire execState.
private set programMemory(programMemory) {
this._programMemory = programMemory this._programMemory = programMemory
this._programMemoryCallBack(programMemory) this._programMemoryCallBack(programMemory)
} }
set execState(execState) {
this._execState = execState
this.programMemory = execState.memory
}
get execState() {
return this._execState
}
get logs() { get logs() {
return this._logs return this._logs
} }
@ -253,8 +266,9 @@ export class KclManager {
// Make sure we clear before starting again. End session will do this. // Make sure we clear before starting again. End session will do this.
this.engineCommandManager?.endSession() this.engineCommandManager?.endSession()
await this.ensureWasmInit() await this.ensureWasmInit()
const { logs, errors, programMemory, isInterrupted } = await executeAst({ const { logs, errors, execState, isInterrupted } = await executeAst({
ast, ast,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
}) })
@ -264,7 +278,7 @@ export class KclManager {
this.lints = await lintAst({ ast: ast }) this.lints = await lintAst({ ast: ast })
sceneInfra.modelingSend({ type: 'code edit during sketch' }) sceneInfra.modelingSend({ type: 'code edit during sketch' })
defaultSelectionFilter(programMemory, this.engineCommandManager) defaultSelectionFilter(execState.memory, this.engineCommandManager)
if (args.zoomToFit) { if (args.zoomToFit) {
let zoomObjectId: string | undefined = '' let zoomObjectId: string | undefined = ''
@ -295,12 +309,20 @@ export class KclManager {
this._cancelTokens.delete(currentExecutionId) this._cancelTokens.delete(currentExecutionId)
return return
} }
// Exit sketch mode if the AST is empty
if (this._isAstEmpty(ast)) {
await this.disableSketchMode()
}
this.logs = logs this.logs = logs
// Do not add the errors since the program was interrupted and the error is not a real KCL error // Do not add the errors since the program was interrupted and the error is not a real KCL error
this.addKclErrors(isInterrupted ? [] : errors) this.addKclErrors(isInterrupted ? [] : errors)
this.programMemory = programMemory // Reset the next ID index so that we reuse the previous IDs next time.
execState.idGenerator.nextId = 0
this.execState = execState
if (!errors.length) { if (!errors.length) {
this.lastSuccessfulProgramMemory = programMemory this.lastSuccessfulProgramMemory = execState.memory
} }
this.ast = { ...ast } this.ast = { ...ast }
this._executeCallback() this._executeCallback()
@ -338,17 +360,19 @@ export class KclManager {
await codeManager.writeToFile() await codeManager.writeToFile()
this._ast = { ...newAst } this._ast = { ...newAst }
const { logs, errors, programMemory } = await executeAst({ const { logs, errors, execState } = await executeAst({
ast: newAst, ast: newAst,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
useFakeExecutor: true, useFakeExecutor: true,
}) })
this._logs = logs this._logs = logs
this._kclErrors = errors this._kclErrors = errors
this._programMemory = programMemory this._execState = execState
this._programMemory = execState.memory
if (!errors.length) { if (!errors.length) {
this.lastSuccessfulProgramMemory = programMemory this.lastSuccessfulProgramMemory = execState.memory
} }
if (updates !== 'artifactRanges') return if (updates !== 'artifactRanges') return
@ -553,6 +577,24 @@ export class KclManager {
defaultSelectionFilter() { defaultSelectionFilter() {
defaultSelectionFilter(this.programMemory, this.engineCommandManager) defaultSelectionFilter(this.programMemory, this.engineCommandManager)
} }
/**
* We can send a single command of 'enable_sketch_mode' or send this in a batched request.
* When there is no code in the KCL editor we should be sending 'sketch_mode_disable' since any previous half finished
* code could leave the state of the application in sketch mode on the engine side.
*/
async disableSketchMode() {
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'sketch_mode_disable' },
})
}
// Determines if there is no KCL code which means it is executing a blank KCL file
_isAstEmpty(ast: Program) {
return ast.start === 0 && ast.end === 0 && ast.body.length === 0
}
} }
function defaultSelectionFilter( function defaultSelectionFilter(

View File

@ -14,9 +14,9 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([-1.59, -1.54], %) |> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %) |> lineTo([0.46, -5.82], %)
// |> rx(45, %)` // |> rx(45, %)`
const programMemory = await enginelessExecutor(parse(code)) const execState = await enginelessExecutor(parse(code))
// @ts-ignore // @ts-ignore
const sketch001 = programMemory?.get('mySketch001') const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
type: 'UserVal', type: 'UserVal',
__meta: [{ sourceRange: [46, 71] }], __meta: [{ sourceRange: [46, 71] }],
@ -68,9 +68,9 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([0.46, -5.82], %) |> lineTo([0.46, -5.82], %)
// |> rx(45, %) // |> rx(45, %)
|> extrude(2, %)` |> extrude(2, %)`
const programMemory = await enginelessExecutor(parse(code)) const execState = await enginelessExecutor(parse(code))
// @ts-ignore // @ts-ignore
const sketch001 = programMemory?.get('mySketch001') const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
type: 'Solid', type: 'Solid',
id: expect.any(String), id: expect.any(String),
@ -148,9 +148,10 @@ const sk2 = startSketchOn('XY')
|> extrude(2, %) |> extrude(2, %)
` `
const programMemory = await enginelessExecutor(parse(code)) const execState = await enginelessExecutor(parse(code))
const programMemory = execState.memory
// @ts-ignore // @ts-ignore
const geos = [programMemory?.get('theExtrude'), programMemory?.get('sk2')] const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
expect(geos).toEqual([ expect(geos).toEqual([
{ {
type: 'Solid', type: 'Solid',

View File

@ -443,6 +443,6 @@ async function exe(
) { ) {
const ast = parse(code) const ast = parse(code)
const result = await enginelessExecutor(ast, programMemory) const execState = await enginelessExecutor(ast, programMemory)
return result return execState.memory
} }

View File

@ -4,11 +4,14 @@ import {
ProgramMemory, ProgramMemory,
programMemoryInit, programMemoryInit,
kclLint, kclLint,
emptyExecState,
ExecState,
} from 'lang/wasm' } from 'lang/wasm'
import { enginelessExecutor } from 'lib/testHelpers' import { enginelessExecutor } from 'lib/testHelpers'
import { EngineCommandManager } from 'lang/std/engineConnection' import { EngineCommandManager } from 'lang/std/engineConnection'
import { KCLError } from 'lang/errors' import { KCLError } from 'lang/errors'
import { Diagnostic } from '@codemirror/lint' import { Diagnostic } from '@codemirror/lint'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
export type ToolTip = export type ToolTip =
| 'lineTo' | 'lineTo'
@ -47,16 +50,18 @@ export async function executeAst({
engineCommandManager, engineCommandManager,
useFakeExecutor = false, useFakeExecutor = false,
programMemoryOverride, programMemoryOverride,
idGenerator,
}: { }: {
ast: Program ast: Program
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
useFakeExecutor?: boolean useFakeExecutor?: boolean
programMemoryOverride?: ProgramMemory programMemoryOverride?: ProgramMemory
idGenerator?: IdGenerator
isInterrupted?: boolean isInterrupted?: boolean
}): Promise<{ }): Promise<{
logs: string[] logs: string[]
errors: KCLError[] errors: KCLError[]
programMemory: ProgramMemory execState: ExecState
isInterrupted: boolean isInterrupted: boolean
}> { }> {
try { try {
@ -65,15 +70,21 @@ export async function executeAst({
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession() engineCommandManager.startNewSession()
} }
const programMemory = await (useFakeExecutor const execState = await (useFakeExecutor
? enginelessExecutor(ast, programMemoryOverride || programMemoryInit()) ? enginelessExecutor(ast, programMemoryOverride || programMemoryInit())
: _executor(ast, programMemoryInit(), engineCommandManager, false)) : _executor(
ast,
programMemoryInit(),
idGenerator,
engineCommandManager,
false
))
await engineCommandManager.waitForAllCommands() await engineCommandManager.waitForAllCommands()
return { return {
logs: [], logs: [],
errors: [], errors: [],
programMemory, execState,
isInterrupted: false, isInterrupted: false,
} }
} catch (e: any) { } catch (e: any) {
@ -89,7 +100,7 @@ export async function executeAst({
return { return {
errors: [e], errors: [e],
logs: [], logs: [],
programMemory: ProgramMemory.empty(), execState: emptyExecState(),
isInterrupted, isInterrupted,
} }
} else { } else {
@ -97,7 +108,7 @@ export async function executeAst({
return { return {
logs: [e], logs: [e],
errors: [], errors: [],
programMemory: ProgramMemory.empty(), execState: emptyExecState(),
isInterrupted, isInterrupted,
} }
} }

View File

@ -220,11 +220,11 @@ yo2 = hmm([identifierGuy + 5])`
it('should move a binary expression into a new variable', async () => { it('should move a binary expression into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('100 + 100') + 1 const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -235,11 +235,11 @@ yo2 = hmm([identifierGuy + 5])`
it('should move a value into a new variable', async () => { it('should move a value into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('2.8') + 1 const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -250,11 +250,11 @@ yo2 = hmm([identifierGuy + 5])`
it('should move a callExpression into a new variable', async () => { it('should move a callExpression into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('def(') const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -265,11 +265,11 @@ yo2 = hmm([identifierGuy + 5])`
it('should move a binary expression with call expression into a new variable', async () => { it('should move a binary expression with call expression into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('jkl(') + 1 const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -280,11 +280,11 @@ yo2 = hmm([identifierGuy + 5])`
it('should move a identifier into a new variable', async () => { it('should move a identifier into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('identifierGuy +') + 1 const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -465,7 +465,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|> line([306.21, 198.87], %)` |> line([306.21, 198.87], %)`
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = 'line([306.21, 198.85], %, $a)' const lineOfInterest = 'line([306.21, 198.85], %, $a)'
const range: [number, number] = [ const range: [number, number] = [
code.indexOf(lineOfInterest), code.indexOf(lineOfInterest),
@ -475,7 +475,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
const modifiedAst = deleteSegmentFromPipeExpression( const modifiedAst = deleteSegmentFromPipeExpression(
[], [],
ast, ast,
programMemory, execState.memory,
code, code,
pathToNode pathToNode
) )
@ -543,7 +543,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
const code = makeCode(line) const code = makeCode(line)
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = line const lineOfInterest = line
const range: [number, number] = [ const range: [number, number] = [
code.indexOf(lineOfInterest), code.indexOf(lineOfInterest),
@ -554,7 +554,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
const modifiedAst = deleteSegmentFromPipeExpression( const modifiedAst = deleteSegmentFromPipeExpression(
dependentSegments, dependentSegments,
ast, ast,
programMemory, execState.memory,
code, code,
pathToNode pathToNode
) )
@ -632,7 +632,7 @@ describe('Testing removeSingleConstraintInfo', () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '(' const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number] = [ const range: [number, number] = [
code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + 1,
@ -661,7 +661,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode, pathToNode,
argPosition, argPosition,
ast, ast,
programMemory execState.memory
) )
if (!mod) return new Error('mod is undefined') if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst) const recastCode = recast(mod.modifiedAst)
@ -686,7 +686,7 @@ describe('Testing removeSingleConstraintInfo', () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '(' const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number] = [ const range: [number, number] = [
code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + 1,
@ -711,7 +711,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode, pathToNode,
argPosition, argPosition,
ast, ast,
programMemory execState.memory
) )
if (!mod) return new Error('mod is undefined') if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst) const recastCode = recast(mod.modifiedAst)
@ -882,7 +882,7 @@ sketch002 = startSketchOn({
// const lineOfInterest = 'line([-2.94, 2.7], %)' // const lineOfInterest = 'line([-2.94, 2.7], %)'
const ast = parse(codeBefore) const ast = parse(codeBefore)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
// deleteFromSelection // deleteFromSelection
const range: [number, number] = [ const range: [number, number] = [
@ -895,7 +895,7 @@ sketch002 = startSketchOn({
range, range,
type, type,
}, },
programMemory, execState.memory,
async () => { async () => {
await new Promise((resolve) => setTimeout(resolve, 100)) await new Promise((resolve) => setTimeout(resolve, 100))
return { return {

View File

@ -45,11 +45,11 @@ variableBelowShouldNotBeIncluded = 3
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7 const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const { variables, bodyPath, insertIndex } = findAllPreviousVariables( const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
ast, ast,
programMemory, execState.memory,
[rangeStart, rangeStart] [rangeStart, rangeStart]
) )
expect(variables).toEqual([ expect(variables).toEqual([
@ -351,11 +351,11 @@ part001 = startSketchAt([-1.41, 3.46])
const ast = parse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({ const result = hasExtrudeSketch({
ast, ast,
selection: { type: 'default', range: [100, 101] }, selection: { type: 'default', range: [100, 101] },
programMemory, programMemory: execState.memory,
}) })
expect(result).toEqual(true) expect(result).toEqual(true)
}) })
@ -370,11 +370,11 @@ part001 = startSketchAt([-1.41, 3.46])
const ast = parse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({ const result = hasExtrudeSketch({
ast, ast,
selection: { type: 'default', range: [100, 101] }, selection: { type: 'default', range: [100, 101] },
programMemory, programMemory: execState.memory,
}) })
expect(result).toEqual(true) expect(result).toEqual(true)
}) })
@ -383,11 +383,11 @@ part001 = startSketchAt([-1.41, 3.46])
const ast = parse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({ const result = hasExtrudeSketch({
ast, ast,
selection: { type: 'default', range: [10, 11] }, selection: { type: 'default', range: [10, 11] },
programMemory, programMemory: execState.memory,
}) })
expect(result).toEqual(false) expect(result).toEqual(false)
}) })

View File

@ -117,11 +117,11 @@ describe('testing changeSketchArguments', () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) return ast if (err(ast)) return ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange) const sourceStart = code.indexOf(lineToChange)
const changeSketchArgsRetVal = changeSketchArguments( const changeSketchArgsRetVal = changeSketchArguments(
ast, ast,
programMemory, execState.memory,
{ {
type: 'sourceRange', type: 'sourceRange',
sourceRange: [sourceStart, sourceStart + lineToChange.length], sourceRange: [sourceStart, sourceStart + lineToChange.length],
@ -150,12 +150,12 @@ mySketch001 = startSketchOn('XY')
const ast = parse(code) const ast = parse(code)
if (err(ast)) return ast if (err(ast)) return ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange) const sourceStart = code.indexOf(lineToChange)
expect(sourceStart).toBe(89) expect(sourceStart).toBe(89)
const newSketchLnRetVal = addNewSketchLn({ const newSketchLnRetVal = addNewSketchLn({
node: ast, node: ast,
programMemory, programMemory: execState.memory,
input: { input: {
type: 'straight-segment', type: 'straight-segment',
from: [0, 0], from: [0, 0],
@ -186,7 +186,7 @@ mySketch001 = startSketchOn('XY')
const modifiedAst2 = addCloseToPipe({ const modifiedAst2 = addCloseToPipe({
node: ast, node: ast,
programMemory, programMemory: execState.memory,
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
@ -230,7 +230,7 @@ describe('testing addTagForSketchOnFace', () => {
const pathToNode = getNodePathFromSourceRange(ast, sourceRange) const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
const sketchOnFaceRetVal = addTagForSketchOnFace( const sketchOnFaceRetVal = addTagForSketchOnFace(
{ {
// previousProgramMemory: programMemory, // redundant? // previousProgramMemory: execState.memory, // redundant?
pathToNode, pathToNode,
node: ast, node: ast,
}, },

View File

@ -34,7 +34,7 @@ async function testingSwapSketchFnCall({
const ast = parse(inputCode) const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast) if (err(ast)) return Promise.reject(ast)
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const selections = { const selections = {
codeBasedSelections: [range], codeBasedSelections: [range],
otherSelections: [], otherSelections: [],
@ -45,7 +45,7 @@ async function testingSwapSketchFnCall({
return Promise.reject(new Error('transformInfos undefined')) return Promise.reject(new Error('transformInfos undefined'))
const ast2 = transformAstSketchLines({ const ast2 = transformAstSketchLines({
ast, ast,
programMemory, programMemory: execState.memory,
selectionRanges: selections, selectionRanges: selections,
transformInfos, transformInfos,
referenceSegName: '', referenceSegName: '',
@ -360,10 +360,10 @@ part001 = startSketchOn('XY')
|> line([2.14, 1.35], %) // normal-segment |> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)` |> xLine(3.54, %)`
it('normal case works', async () => { it('normal case works', async () => {
const programMemory = await enginelessExecutor(parse(code)) const execState = await enginelessExecutor(parse(code))
const index = code.indexOf('// normal-segment') - 7 const index = code.indexOf('// normal-segment') - 7
const sg = sketchFromKclValue( const sg = sketchFromKclValue(
programMemory.get('part001'), execState.memory.get('part001'),
'part001' 'part001'
) as Sketch ) as Sketch
const _segment = getSketchSegmentFromSourceRange(sg, [index, index]) const _segment = getSketchSegmentFromSourceRange(sg, [index, index])
@ -377,10 +377,10 @@ part001 = startSketchOn('XY')
}) })
}) })
it('verify it works when the segment is in the `start` property', async () => { it('verify it works when the segment is in the `start` property', async () => {
const programMemory = await enginelessExecutor(parse(code)) const execState = await enginelessExecutor(parse(code))
const index = code.indexOf('// segment-in-start') - 7 const index = code.indexOf('// segment-in-start') - 7
const _segment = getSketchSegmentFromSourceRange( const _segment = getSketchSegmentFromSourceRange(
sketchFromKclValue(programMemory.get('part001'), 'part001') as Sketch, sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
[index, index] [index, index]
) )
if (err(_segment)) throw _segment if (err(_segment)) throw _segment

View File

@ -220,7 +220,7 @@ part001 = startSketchOn('XY')
} }
}) })
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const transformInfos = getTransformInfos( const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)), makeSelections(selectionRanges.slice(1)),
ast, ast,
@ -231,7 +231,7 @@ part001 = startSketchOn('XY')
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory, programMemory: execState.memory,
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)
@ -311,7 +311,7 @@ part001 = startSketchOn('XY')
} }
}) })
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const transformInfos = getTransformInfos( const transformInfos = getTransformInfos(
makeSelections(selectionRanges), makeSelections(selectionRanges),
ast, ast,
@ -322,7 +322,7 @@ part001 = startSketchOn('XY')
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory, programMemory: execState.memory,
referenceSegName: '', referenceSegName: '',
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)
@ -373,7 +373,7 @@ part001 = startSketchOn('XY')
} }
}) })
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const transformInfos = getTransformInfos( const transformInfos = getTransformInfos(
makeSelections(selectionRanges), makeSelections(selectionRanges),
ast, ast,
@ -384,7 +384,7 @@ part001 = startSketchOn('XY')
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory, programMemory: execState.memory,
referenceSegName: '', referenceSegName: '',
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)
@ -470,7 +470,7 @@ async function helperThing(
} }
}) })
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const transformInfos = getTransformInfos( const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)), makeSelections(selectionRanges.slice(1)),
ast, ast,
@ -481,7 +481,7 @@ async function helperThing(
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory, programMemory: execState.memory,
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)

View File

@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
offset: ${offset}, offset: ${offset},
}, %, $yo2) }, %, $yo2)
intersect = segEndX(yo2)` intersect = segEndX(yo2)`
const mem = await enginelessExecutor(parse(code('-1'))) const execState = await enginelessExecutor(parse(code('-1')))
expect(mem.get('intersect')?.value).toBe(1 + Math.sqrt(2)) expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2))
const noOffset = await enginelessExecutor(parse(code('0'))) const noOffset = await enginelessExecutor(parse(code('0')))
expect(noOffset.get('intersect')?.value).toBeCloseTo(1) expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
}) })
}) })

View File

@ -37,6 +37,11 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { DeepPartial } from 'lib/types' import { DeepPartial } from 'lib/types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration' import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
import { Sketch } from '../wasm-lib/kcl/bindings/Sketch' import { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { ExecState as RawExecState } from '../wasm-lib/kcl/bindings/ExecState'
import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory'
import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
export type { Program } from '../wasm-lib/kcl/bindings/Program' export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr' export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
@ -136,29 +141,46 @@ export const parse = (code: string | Error): Program | Error => {
export type PathToNode = [string | number, string][] export type PathToNode = [string | number, string][]
interface Memory { export interface ExecState {
[key: string]: KclValue memory: ProgramMemory
idGenerator: IdGenerator
} }
type EnvironmentRef = number /**
* Create an empty ExecState. This is useful on init to prevent needing an
* Option.
*/
export function emptyExecState(): ExecState {
return {
memory: ProgramMemory.empty(),
idGenerator: defaultIdGenerator(),
}
}
function execStateFromRaw(raw: RawExecState): ExecState {
return {
memory: ProgramMemory.fromRaw(raw.memory),
idGenerator: raw.idGenerator,
}
}
export function defaultIdGenerator(): IdGenerator {
return {
nextId: 0,
ids: [],
}
}
interface Memory {
[key: string]: KclValue | undefined
}
const ROOT_ENVIRONMENT_REF: EnvironmentRef = 0 const ROOT_ENVIRONMENT_REF: EnvironmentRef = 0
interface Environment {
bindings: Memory
parent: EnvironmentRef | null
}
function emptyEnvironment(): Environment { function emptyEnvironment(): Environment {
return { bindings: {}, parent: null } return { bindings: {}, parent: null }
} }
interface RawProgramMemory {
environments: Environment[]
currentEnv: EnvironmentRef
return: KclValue | null
}
/** /**
* This duplicates logic in Rust. The hope is to keep ProgramMemory internals * This duplicates logic in Rust. The hope is to keep ProgramMemory internals
* isolated from the rest of the TypeScript code so that we can move it to Rust * isolated from the rest of the TypeScript code so that we can move it to Rust
@ -217,7 +239,7 @@ export class ProgramMemory {
while (true) { while (true) {
const env = this.environments[envRef] const env = this.environments[envRef]
if (env.bindings.hasOwnProperty(name)) { if (env.bindings.hasOwnProperty(name)) {
return env.bindings[name] return env.bindings[name] ?? null
} }
if (!env.parent) { if (!env.parent) {
break break
@ -260,6 +282,7 @@ export class ProgramMemory {
} }
for (const [name, value] of Object.entries(env.bindings)) { for (const [name, value] of Object.entries(env.bindings)) {
if (value === undefined) continue
// Check the predicate. // Check the predicate.
if (!predicate(value)) { if (!predicate(value)) {
continue continue
@ -293,6 +316,7 @@ export class ProgramMemory {
while (true) { while (true) {
const env = this.environments[envRef] const env = this.environments[envRef]
for (const [name, value] of Object.entries(env.bindings)) { for (const [name, value] of Object.entries(env.bindings)) {
if (value === undefined) continue
// Don't include shadowed variables. // Don't include shadowed variables.
if (!map.has(name)) { if (!map.has(name)) {
map.set(name, value) map.set(name, value)
@ -356,9 +380,10 @@ export function sketchFromKclValue(
export const executor = async ( export const executor = async (
node: Program, node: Program,
programMemory: ProgramMemory | Error = ProgramMemory.empty(), programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
isMock: boolean = false isMock: boolean = false
): Promise<ProgramMemory> => { ): Promise<ExecState> => {
if (err(programMemory)) return Promise.reject(programMemory) if (err(programMemory)) return Promise.reject(programMemory)
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
@ -366,6 +391,7 @@ export const executor = async (
const _programMemory = await _executor( const _programMemory = await _executor(
node, node,
programMemory, programMemory,
idGenerator,
engineCommandManager, engineCommandManager,
isMock isMock
) )
@ -378,9 +404,10 @@ export const executor = async (
export const _executor = async ( export const _executor = async (
node: Program, node: Program,
programMemory: ProgramMemory | Error = ProgramMemory.empty(), programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
isMock: boolean isMock: boolean
): Promise<ProgramMemory> => { ): Promise<ExecState> => {
if (err(programMemory)) return Promise.reject(programMemory) if (err(programMemory)) return Promise.reject(programMemory)
try { try {
@ -392,15 +419,16 @@ export const _executor = async (
baseUnit = baseUnit =
(await getSettingsState)()?.modeling.defaultUnit.current || 'mm' (await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
} }
const memory: RawProgramMemory = await execute_wasm( const execState: RawExecState = await execute_wasm(
JSON.stringify(node), JSON.stringify(node),
JSON.stringify(programMemory.toRaw()), JSON.stringify(programMemory.toRaw()),
JSON.stringify(idGenerator),
baseUnit, baseUnit,
engineCommandManager, engineCommandManager,
fileSystemManager, fileSystemManager,
isMock isMock
) )
return ProgramMemory.fromRaw(memory) return execStateFromRaw(execState)
} catch (e: any) { } catch (e: any) {
console.log(e) console.log(e)
const parsed: RustKclError = JSON.parse(e.toString()) const parsed: RustKclError = JSON.parse(e.toString())

View File

@ -106,3 +106,6 @@ export const KCL_SAMPLES_MANIFEST_URLS = {
/** URL parameter to create a file */ /** URL parameter to create a file */
export const CREATE_FILE_URL_PARAM = 'create-file' export const CREATE_FILE_URL_PARAM = 'create-file'
/** Toast id for the app auto-updater toast */
export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast'

View File

@ -1,4 +1,11 @@
import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm' import {
Program,
ProgramMemory,
_executor,
SourceRange,
ExecState,
defaultIdGenerator,
} from '../lang/wasm'
import { import {
EngineCommandManager, EngineCommandManager,
EngineCommandManagerEvents, EngineCommandManagerEvents,
@ -9,6 +16,7 @@ import { v4 as uuidv4 } from 'uuid'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes' import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { err, reportRejection } from 'lib/trap' import { err, reportRejection } from 'lib/trap'
import { toSync } from './utils' import { toSync } from './utils'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
type WebSocketResponse = Models['WebSocketResponse_type'] type WebSocketResponse = Models['WebSocketResponse_type']
@ -77,8 +85,9 @@ class MockEngineCommandManager {
export async function enginelessExecutor( export async function enginelessExecutor(
ast: Program | Error, ast: Program | Error,
pm: ProgramMemory | Error = ProgramMemory.empty() pm: ProgramMemory | Error = ProgramMemory.empty(),
): Promise<ProgramMemory> { idGenerator: IdGenerator = defaultIdGenerator()
): Promise<ExecState> {
if (err(ast)) return Promise.reject(ast) if (err(ast)) return Promise.reject(ast)
if (err(pm)) return Promise.reject(pm) if (err(pm)) return Promise.reject(pm)
@ -88,15 +97,22 @@ export async function enginelessExecutor(
}) as any as EngineCommandManager }) as any as EngineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
mockEngineCommandManager.startNewSession() mockEngineCommandManager.startNewSession()
const programMemory = await _executor(ast, pm, mockEngineCommandManager, true) const execState = await _executor(
ast,
pm,
idGenerator,
mockEngineCommandManager,
true
)
await mockEngineCommandManager.waitForAllCommands() await mockEngineCommandManager.waitForAllCommands()
return programMemory return execState
} }
export async function executor( export async function executor(
ast: Program, ast: Program,
pm: ProgramMemory = ProgramMemory.empty() pm: ProgramMemory = ProgramMemory.empty(),
): Promise<ProgramMemory> { idGenerator: IdGenerator = defaultIdGenerator()
): Promise<ExecState> {
const engineCommandManager = new EngineCommandManager() const engineCommandManager = new EngineCommandManager()
engineCommandManager.start({ engineCommandManager.start({
setIsStreamReady: () => {}, setIsStreamReady: () => {},
@ -117,14 +133,15 @@ export async function executor(
toSync(async () => { toSync(async () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession() engineCommandManager.startNewSession()
const programMemory = await _executor( const execState = await _executor(
ast, ast,
pm, pm,
idGenerator,
engineCommandManager, engineCommandManager,
false false
) )
await engineCommandManager.waitForAllCommands() await engineCommandManager.waitForAllCommands()
resolve(programMemory) resolve(execState)
}, reportRejection) }, reportRejection)
) )
}) })

View File

@ -295,15 +295,24 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
'break', 'break',
{ {
id: 'line', id: 'line',
onClick: ({ modelingState, modelingSend }) => onClick: ({ modelingState, modelingSend }) => {
modelingSend({ if (modelingState.matches({ Sketch: { 'Line tool': 'No Points' } })) {
type: 'change tool', // Exit the sketch state if there are no points and they press ESC
data: { modelingSend({
tool: !modelingState.matches({ Sketch: 'Line tool' }) type: 'Cancel',
? 'line' })
: 'none', } else {
}, // Exit the tool if there are points and they press ESC
}), modelingSend({
type: 'change tool',
data: {
tool: !modelingState.matches({ Sketch: 'Line tool' })
? 'line'
: 'none',
},
})
}
},
icon: 'line', icon: 'line',
status: 'available', status: 'available',
disabled: (state) => disabled: (state) =>

View File

@ -97,7 +97,7 @@ export function useCalculateKclExpression({
}) })
if (trap(error, { suppress: true })) return if (trap(error, { suppress: true })) return
} }
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast, ast,
engineCommandManager, engineCommandManager,
useFakeExecutor: true, useFakeExecutor: true,
@ -111,7 +111,7 @@ export function useCalculateKclExpression({
const init = const init =
resultDeclaration?.type === 'VariableDeclaration' && resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.get('__result__')?.value const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN') setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init) init && setValueNode(init)
} }

View File

@ -666,6 +666,7 @@ export const modelingMachine = setup({
const testExecute = await executeAst({ const testExecute = await executeAst({
ast: modifiedAst, ast: modifiedAst,
idGenerator: kclManager.execState.idGenerator,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager, engineCommandManager,
}) })

View File

@ -261,10 +261,30 @@ app.on('ready', () => {
autoUpdater.checkForUpdates().catch(reportRejection) autoUpdater.checkForUpdates().catch(reportRejection)
}, fifteenMinutes) }, fifteenMinutes)
autoUpdater.on('error', (error) => {
console.error('updater-error', error)
mainWindow?.webContents.send('updater-error', error)
})
autoUpdater.on('update-available', (info) => { autoUpdater.on('update-available', (info) => {
console.log('update-available', info) console.log('update-available', info)
}) })
autoUpdater.prependOnceListener('download-progress', (progress) => {
// For now, we'll send nothing and just start a loading spinner.
// See below for a TODO to send progress data to the renderer.
console.log('update-download-start', {
version: '',
})
mainWindow?.webContents.send('update-download-start', progress)
})
autoUpdater.on('download-progress', (progress) => {
// TODO: in a future PR (https://github.com/KittyCAD/modeling-app/issues/3994)
// send this data to mainWindow to show a progress bar for the download.
console.log('download-progress', progress)
})
autoUpdater.on('update-downloaded', (info) => { autoUpdater.on('update-downloaded', (info) => {
console.log('update-downloaded', info) console.log('update-downloaded', info)
mainWindow?.webContents.send('update-downloaded', info.version) mainWindow?.webContents.send('update-downloaded', info.version)

View File

@ -16,8 +16,13 @@ const startDeviceFlow = (host: string): Promise<string> =>
ipcRenderer.invoke('startDeviceFlow', host) ipcRenderer.invoke('startDeviceFlow', host)
const loginWithDeviceFlow = (): Promise<string> => const loginWithDeviceFlow = (): Promise<string> =>
ipcRenderer.invoke('loginWithDeviceFlow') ipcRenderer.invoke('loginWithDeviceFlow')
const onUpdateDownloadStart = (
callback: (value: { version: string }) => void
) => ipcRenderer.on('update-download-start', (_event, value) => callback(value))
const onUpdateDownloaded = (callback: (value: string) => void) => const onUpdateDownloaded = (callback: (value: string) => void) =>
ipcRenderer.on('update-downloaded', (_event, value) => callback(value)) ipcRenderer.on('update-downloaded', (_event, value) => callback(value))
const onUpdateError = (callback: (value: Error) => void) =>
ipcRenderer.on('update-error', (_event, value) => callback(value))
const appRestart = () => ipcRenderer.invoke('app.restart') const appRestart = () => ipcRenderer.invoke('app.restart')
const isMac = os.platform() === 'darwin' const isMac = os.platform() === 'darwin'
@ -144,6 +149,8 @@ contextBridge.exposeInMainWorld('electron', {
kittycad, kittycad,
listMachines, listMachines,
getMachineApiIp, getMachineApiIp,
onUpdateDownloadStart,
onUpdateDownloaded, onUpdateDownloaded,
onUpdateError,
appRestart, appRestart,
}) })

View File

@ -434,9 +434,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.19" version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -444,9 +444,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.19" version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",

View File

@ -753,6 +753,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
let tokens = crate::token::lexer(#code_block).unwrap(); let tokens = crate::token::lexer(#code_block).unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())), engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
@ -761,7 +762,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_someFn {
let tokens = crate::token::lexer("someFn()").unwrap(); let tokens = crate::token::lexer("someFn()").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_someFn {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_someFn {
let tokens = crate::token::lexer("someFn()").unwrap(); let tokens = crate::token::lexer("someFn()").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_someFn {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_show {
let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nshow").unwrap(); let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nshow").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -38,6 +39,7 @@ mod test_examples_show {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -49,7 +51,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_show {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -6,6 +6,7 @@ mod test_examples_my_func {
crate::token::lexer("This is another code block.\nyes sirrr.\nmyFunc").unwrap(); crate::token::lexer("This is another code block.\nyes sirrr.\nmyFunc").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -17,7 +18,7 @@ mod test_examples_my_func {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -39,6 +40,7 @@ mod test_examples_my_func {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmyFunc").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmyFunc").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -50,7 +52,7 @@ mod test_examples_my_func {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -6,6 +6,7 @@ mod test_examples_line_to {
crate::token::lexer("This is another code block.\nyes sirrr.\nlineTo").unwrap(); crate::token::lexer("This is another code block.\nyes sirrr.\nlineTo").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -17,7 +18,7 @@ mod test_examples_line_to {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -39,6 +40,7 @@ mod test_examples_line_to {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nlineTo").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nlineTo").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -50,7 +52,7 @@ mod test_examples_line_to {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_min {
let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nmin").unwrap(); let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nmin").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_min {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -38,6 +39,7 @@ mod test_examples_min {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmin").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmin").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -49,7 +51,7 @@ mod test_examples_min {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_show {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_import {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_import {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_import {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_show {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap(); let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -5,6 +5,7 @@ mod test_examples_some_function {
let tokens = crate::token::lexer("someFunction()").unwrap(); let tokens = crate::token::lexer("someFunction()").unwrap();
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +17,7 @@ mod test_examples_some_function {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -166,15 +166,19 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
Err(e) => return bad_request(format!("Parse error: {e}")), Err(e) => return bad_request(format!("Parse error: {e}")),
}; };
eprintln!("Executing {test_name}"); eprintln!("Executing {test_name}");
let mut id_generator = kcl_lib::executor::IdGenerator::default();
// This is a shitty source range, I don't know what else to use for it though. // This is a shitty source range, I don't know what else to use for it though.
// There's no actual KCL associated with this reset_scene call. // There's no actual KCL associated with this reset_scene call.
if let Err(e) = state.reset_scene(kcl_lib::executor::SourceRange::default()).await { if let Err(e) = state
.reset_scene(&mut id_generator, kcl_lib::executor::SourceRange::default())
.await
{
return kcl_err(e); return kcl_err(e);
} }
// Let users know if the test is taking a long time. // Let users know if the test is taking a long time.
let (done_tx, done_rx) = oneshot::channel::<()>(); let (done_tx, done_rx) = oneshot::channel::<()>();
let timer = time_until(done_rx); let timer = time_until(done_rx);
let snapshot = match state.execute_and_prepare_snapshot(&program).await { let snapshot = match state.execute_and_prepare_snapshot(&program, id_generator).await {
Ok(sn) => sn, Ok(sn) => sn,
Err(e) => return kcl_err(e), Err(e) => return kcl_err(e),
}; };

View File

@ -1,6 +1,9 @@
use anyhow::Result; use anyhow::Result;
use indexmap::IndexMap; use indexmap::IndexMap;
use kcl_lib::{errors::KclError, executor::DefaultPlanes}; use kcl_lib::{
errors::KclError,
executor::{DefaultPlanes, IdGenerator},
};
use kittycad_modeling_cmds::{ use kittycad_modeling_cmds::{
self as kcmc, self as kcmc,
id::ModelingCmdId, id::ModelingCmdId,
@ -357,7 +360,11 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
self.batch_end.clone() self.batch_end.clone()
} }
async fn default_planes(&self, source_range: kcl_lib::executor::SourceRange) -> Result<DefaultPlanes, KclError> { async fn default_planes(
&self,
id_generator: &mut IdGenerator,
source_range: kcl_lib::executor::SourceRange,
) -> Result<DefaultPlanes, KclError> {
if NEED_PLANES { if NEED_PLANES {
{ {
let opt = self.default_planes.read().await.as_ref().cloned(); let opt = self.default_planes.read().await.as_ref().cloned();
@ -366,7 +373,7 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
} }
} // drop the read lock } // drop the read lock
let new_planes = self.new_default_planes(source_range).await?; let new_planes = self.new_default_planes(id_generator, source_range).await?;
*self.default_planes.write().await = Some(new_planes.clone()); *self.default_planes.write().await = Some(new_planes.clone());
Ok(new_planes) Ok(new_planes)
@ -375,7 +382,11 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
} }
} }
async fn clear_scene_post_hook(&self, _source_range: kcl_lib::executor::SourceRange) -> Result<(), KclError> { async fn clear_scene_post_hook(
&self,
_id_generator: &mut IdGenerator,
_source_range: kcl_lib::executor::SourceRange,
) -> Result<(), KclError> {
Ok(()) Ok(())
} }

View File

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use kcl_lib::executor::ExecutorContext; use kcl_lib::executor::{ExecutorContext, IdGenerator};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -23,7 +23,7 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
settings: Default::default(), settings: Default::default(),
context_type: kcl_lib::executor::ContextType::MockCustomForwarded, context_type: kcl_lib::executor::ContextType::MockCustomForwarded,
}; };
let _memory = ctx.run(&program, None).await?; let _memory = ctx.run(&program, None, IdGenerator::default()).await?;
let result = result.lock().expect("mutex lock").clone(); let result = result.lock().expect("mutex lock").clone();
Ok(result) Ok(result)

View File

@ -16,7 +16,7 @@ async-recursion = "1.1.1"
async-trait = "0.1.83" async-trait = "0.1.83"
base64 = "0.22.1" base64 = "0.22.1"
chrono = "0.4.38" chrono = "0.4.38"
clap = { version = "4.5.19", default-features = false, optional = true, features = ["std", "derive"] } clap = { version = "4.5.20", default-features = false, optional = true, features = ["std", "derive"] }
convert_case = "0.6.0" convert_case = "0.6.0"
dashmap = "6.1.0" dashmap = "6.1.0"
databake = { version = "0.1.8", features = ["derive"] } databake = { version = "0.1.8", features = ["derive"] }

View File

@ -21,7 +21,7 @@ use tokio_tungstenite::tungstenite::Message as WsMsg;
use crate::{ use crate::{
engine::EngineManager, engine::EngineManager,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::DefaultPlanes, executor::{DefaultPlanes, IdGenerator},
}; };
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -314,7 +314,11 @@ impl EngineManager for EngineConnection {
self.batch_end.clone() self.batch_end.clone()
} }
async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> { async fn default_planes(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
) -> Result<DefaultPlanes, KclError> {
{ {
let opt = self.default_planes.read().await.as_ref().cloned(); let opt = self.default_planes.read().await.as_ref().cloned();
if let Some(planes) = opt { if let Some(planes) = opt {
@ -322,15 +326,19 @@ impl EngineManager for EngineConnection {
} }
} // drop the read lock } // drop the read lock
let new_planes = self.new_default_planes(source_range).await?; let new_planes = self.new_default_planes(id_generator, source_range).await?;
*self.default_planes.write().await = Some(new_planes.clone()); *self.default_planes.write().await = Some(new_planes.clone());
Ok(new_planes) Ok(new_planes)
} }
async fn clear_scene_post_hook(&self, source_range: crate::executor::SourceRange) -> Result<(), KclError> { async fn clear_scene_post_hook(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
) -> Result<(), KclError> {
// Remake the default planes, since they would have been removed after the scene was cleared. // Remake the default planes, since they would have been removed after the scene was cleared.
let new_planes = self.new_default_planes(source_range).await?; let new_planes = self.new_default_planes(id_generator, source_range).await?;
*self.default_planes.write().await = Some(new_planes); *self.default_planes.write().await = Some(new_planes);
Ok(()) Ok(())

View File

@ -17,7 +17,10 @@ use kcmc::{
}; };
use kittycad_modeling_cmds::{self as kcmc}; use kittycad_modeling_cmds::{self as kcmc};
use crate::{errors::KclError, executor::DefaultPlanes}; use crate::{
errors::KclError,
executor::{DefaultPlanes, IdGenerator},
};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EngineConnection { pub struct EngineConnection {
@ -44,11 +47,19 @@ impl crate::engine::EngineManager for EngineConnection {
self.batch_end.clone() self.batch_end.clone()
} }
async fn default_planes(&self, _source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> { async fn default_planes(
&self,
_id_generator: &mut IdGenerator,
_source_range: crate::executor::SourceRange,
) -> Result<DefaultPlanes, KclError> {
Ok(DefaultPlanes::default()) Ok(DefaultPlanes::default())
} }
async fn clear_scene_post_hook(&self, _source_range: crate::executor::SourceRange) -> Result<(), KclError> { async fn clear_scene_post_hook(
&self,
_id_generator: &mut IdGenerator,
_source_range: crate::executor::SourceRange,
) -> Result<(), KclError> {
Ok(()) Ok(())
} }

View File

@ -10,7 +10,7 @@ use wasm_bindgen::prelude::*;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::DefaultPlanes, executor::{DefaultPlanes, IdGenerator},
}; };
#[wasm_bindgen(module = "/../../lang/std/engineConnection.ts")] #[wasm_bindgen(module = "/../../lang/std/engineConnection.ts")]
@ -68,7 +68,11 @@ impl crate::engine::EngineManager for EngineConnection {
self.batch_end.clone() self.batch_end.clone()
} }
async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> { async fn default_planes(
&self,
_id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
) -> Result<DefaultPlanes, KclError> {
// Get the default planes. // Get the default planes.
let promise = self.manager.get_default_planes().map_err(|e| { let promise = self.manager.get_default_planes().map_err(|e| {
KclError::Engine(KclErrorDetails { KclError::Engine(KclErrorDetails {
@ -106,7 +110,11 @@ impl crate::engine::EngineManager for EngineConnection {
Ok(default_planes) Ok(default_planes)
} }
async fn clear_scene_post_hook(&self, source_range: crate::executor::SourceRange) -> Result<(), KclError> { async fn clear_scene_post_hook(
&self,
_id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
) -> Result<(), KclError> {
self.manager.clear_default_planes().map_err(|e| { self.manager.clear_default_planes().map_err(|e| {
KclError::Engine(KclErrorDetails { KclError::Engine(KclErrorDetails {
message: e.to_string().into(), message: e.to_string().into(),

View File

@ -32,7 +32,7 @@ use uuid::Uuid;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, Point3d}, executor::{DefaultPlanes, IdGenerator, Point3d},
}; };
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -52,6 +52,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
/// Get the default planes. /// Get the default planes.
async fn default_planes( async fn default_planes(
&self, &self,
id_generator: &mut IdGenerator,
_source_range: crate::executor::SourceRange, _source_range: crate::executor::SourceRange,
) -> Result<DefaultPlanes, crate::errors::KclError>; ) -> Result<DefaultPlanes, crate::errors::KclError>;
@ -59,6 +60,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
/// (These really only apply to wasm for now). /// (These really only apply to wasm for now).
async fn clear_scene_post_hook( async fn clear_scene_post_hook(
&self, &self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange, source_range: crate::executor::SourceRange,
) -> Result<(), crate::errors::KclError>; ) -> Result<(), crate::errors::KclError>;
@ -71,7 +73,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
id_to_source_range: HashMap<uuid::Uuid, crate::executor::SourceRange>, id_to_source_range: HashMap<uuid::Uuid, crate::executor::SourceRange>,
) -> Result<kcmc::websocket::WebSocketResponse, crate::errors::KclError>; ) -> Result<kcmc::websocket::WebSocketResponse, crate::errors::KclError>;
async fn clear_scene(&self, source_range: crate::executor::SourceRange) -> Result<(), crate::errors::KclError> { async fn clear_scene(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
) -> Result<(), crate::errors::KclError> {
self.batch_modeling_cmd( self.batch_modeling_cmd(
uuid::Uuid::new_v4(), uuid::Uuid::new_v4(),
source_range, source_range,
@ -84,7 +90,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
self.flush_batch(false, source_range).await?; self.flush_batch(false, source_range).await?;
// Do the after clear scene hook. // Do the after clear scene hook.
self.clear_scene_post_hook(source_range).await?; self.clear_scene_post_hook(id_generator, source_range).await?;
Ok(()) Ok(())
} }
@ -265,6 +271,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn make_default_plane( async fn make_default_plane(
&self, &self,
plane_id: uuid::Uuid,
x_axis: Point3d, x_axis: Point3d,
y_axis: Point3d, y_axis: Point3d,
color: Option<Color>, color: Option<Color>,
@ -274,7 +281,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
let default_size = 100.0; let default_size = 100.0;
let default_origin = Point3d { x: 0.0, y: 0.0, z: 0.0 }.into(); let default_origin = Point3d { x: 0.0, y: 0.0, z: 0.0 }.into();
let plane_id = uuid::Uuid::new_v4();
self.batch_modeling_cmd( self.batch_modeling_cmd(
plane_id, plane_id,
source_range, source_range,
@ -302,11 +308,16 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
Ok(plane_id) Ok(plane_id)
} }
async fn new_default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> { async fn new_default_planes(
let plane_settings: HashMap<PlaneName, (Point3d, Point3d, Option<Color>)> = HashMap::from([ &self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
) -> Result<DefaultPlanes, KclError> {
let plane_settings: HashMap<PlaneName, (Uuid, Point3d, Point3d, Option<Color>)> = HashMap::from([
( (
PlaneName::Xy, PlaneName::Xy,
( (
id_generator.next_uuid(),
Point3d { x: 1.0, y: 0.0, z: 0.0 }, Point3d { x: 1.0, y: 0.0, z: 0.0 },
Point3d { x: 0.0, y: 1.0, z: 0.0 }, Point3d { x: 0.0, y: 1.0, z: 0.0 },
Some(Color { Some(Color {
@ -320,6 +331,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
( (
PlaneName::Yz, PlaneName::Yz,
( (
id_generator.next_uuid(),
Point3d { x: 0.0, y: 1.0, z: 0.0 }, Point3d { x: 0.0, y: 1.0, z: 0.0 },
Point3d { x: 0.0, y: 0.0, z: 1.0 }, Point3d { x: 0.0, y: 0.0, z: 1.0 },
Some(Color { Some(Color {
@ -333,6 +345,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
( (
PlaneName::Xz, PlaneName::Xz,
( (
id_generator.next_uuid(),
Point3d { x: 1.0, y: 0.0, z: 0.0 }, Point3d { x: 1.0, y: 0.0, z: 0.0 },
Point3d { x: 0.0, y: 0.0, z: 1.0 }, Point3d { x: 0.0, y: 0.0, z: 1.0 },
Some(Color { Some(Color {
@ -346,6 +359,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
( (
PlaneName::NegXy, PlaneName::NegXy,
( (
id_generator.next_uuid(),
Point3d { Point3d {
x: -1.0, x: -1.0,
y: 0.0, y: 0.0,
@ -358,6 +372,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
( (
PlaneName::NegYz, PlaneName::NegYz,
( (
id_generator.next_uuid(),
Point3d { Point3d {
x: 0.0, x: 0.0,
y: -1.0, y: -1.0,
@ -370,6 +385,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
( (
PlaneName::NegXz, PlaneName::NegXz,
( (
id_generator.next_uuid(),
Point3d { Point3d {
x: -1.0, x: -1.0,
y: 0.0, y: 0.0,
@ -382,10 +398,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
]); ]);
let mut planes = HashMap::new(); let mut planes = HashMap::new();
for (name, (x_axis, y_axis, color)) in plane_settings { for (name, (plane_id, x_axis, y_axis, color)) in plane_settings {
planes.insert( planes.insert(
name, name,
self.make_default_plane(x_axis, y_axis, color, source_range).await?, self.make_default_plane(plane_id, x_axis, y_axis, color, source_range)
.await?,
); );
} }

View File

@ -40,6 +40,8 @@ use crate::{
pub struct ExecState { pub struct ExecState {
/// Program variable bindings. /// Program variable bindings.
pub memory: ProgramMemory, pub memory: ProgramMemory,
/// The stable artifact ID generator.
pub id_generator: IdGenerator,
/// Dynamic state that follows dynamic flow of the program. /// Dynamic state that follows dynamic flow of the program.
pub dynamic_state: DynamicState, pub dynamic_state: DynamicState,
/// The current value of the pipe operator returned from the previous /// The current value of the pipe operator returned from the previous
@ -292,7 +294,34 @@ impl DynamicState {
} }
} }
/// A memory item. /// A generator for ArtifactIds that can be stable across executions.
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct IdGenerator {
next_id: usize,
ids: Vec<uuid::Uuid>,
}
impl IdGenerator {
pub fn new() -> Self {
Self::default()
}
pub fn next_uuid(&mut self) -> uuid::Uuid {
if let Some(id) = self.ids.get(self.next_id) {
self.next_id += 1;
*id
} else {
let id = uuid::Uuid::new_v4();
self.ids.push(id);
self.next_id += 1;
id
}
}
}
/// Any KCL value.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -599,6 +628,82 @@ pub struct Plane {
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
} }
impl Plane {
pub(crate) fn from_plane_data(value: crate::std::sketch::PlaneData, exec_state: &mut ExecState) -> Self {
let id = exec_state.id_generator.next_uuid();
match value {
crate::std::sketch::PlaneData::XY => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(1.0, 0.0, 0.0),
y_axis: Point3d::new(0.0, 1.0, 0.0),
z_axis: Point3d::new(0.0, 0.0, 1.0),
value: PlaneType::XY,
meta: vec![],
},
crate::std::sketch::PlaneData::NegXY => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(1.0, 0.0, 0.0),
y_axis: Point3d::new(0.0, 1.0, 0.0),
z_axis: Point3d::new(0.0, 0.0, -1.0),
value: PlaneType::XY,
meta: vec![],
},
crate::std::sketch::PlaneData::XZ => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(1.0, 0.0, 0.0),
y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(0.0, -1.0, 0.0),
value: PlaneType::XZ,
meta: vec![],
},
crate::std::sketch::PlaneData::NegXZ => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(-1.0, 0.0, 0.0),
y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(0.0, 1.0, 0.0),
value: PlaneType::XZ,
meta: vec![],
},
crate::std::sketch::PlaneData::YZ => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(0.0, 1.0, 0.0),
y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(1.0, 0.0, 0.0),
value: PlaneType::YZ,
meta: vec![],
},
crate::std::sketch::PlaneData::NegYZ => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(0.0, 1.0, 0.0),
y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(-1.0, 0.0, 0.0),
value: PlaneType::YZ,
meta: vec![],
},
crate::std::sketch::PlaneData::Plane {
origin,
x_axis,
y_axis,
z_axis,
} => Plane {
id,
origin: *origin,
x_axis: *x_axis,
y_axis: *y_axis,
z_axis: *z_axis,
value: PlaneType::Custom,
meta: vec![],
},
}
}
}
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -1836,8 +1941,12 @@ impl ExecutorContext {
Ok(ctx) Ok(ctx)
} }
pub async fn reset_scene(&self, source_range: crate::executor::SourceRange) -> Result<()> { pub async fn reset_scene(
self.engine.clear_scene(source_range).await?; &self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
) -> Result<()> {
self.engine.clear_scene(id_generator, source_range).await?;
Ok(()) Ok(())
} }
@ -1848,8 +1957,11 @@ impl ExecutorContext {
&self, &self,
program: &crate::ast::types::Program, program: &crate::ast::types::Program,
memory: Option<ProgramMemory>, memory: Option<ProgramMemory>,
id_generator: IdGenerator,
) -> Result<ExecState, KclError> { ) -> Result<ExecState, KclError> {
self.run_with_session_data(program, memory).await.map(|x| x.0) self.run_with_session_data(program, memory, id_generator)
.await
.map(|x| x.0)
} }
/// Perform the execution of a program. /// Perform the execution of a program.
/// You can optionally pass in some initialization memory. /// You can optionally pass in some initialization memory.
@ -1858,11 +1970,22 @@ impl ExecutorContext {
&self, &self,
program: &crate::ast::types::Program, program: &crate::ast::types::Program,
memory: Option<ProgramMemory>, memory: Option<ProgramMemory>,
id_generator: IdGenerator,
) -> Result<(ExecState, Option<ModelingSessionData>), KclError> { ) -> Result<(ExecState, Option<ModelingSessionData>), KclError> {
let memory = if let Some(memory) = memory {
memory.clone()
} else {
Default::default()
};
let mut exec_state = ExecState {
memory,
id_generator,
..Default::default()
};
// Before we even start executing the program, set the units. // Before we even start executing the program, set the units.
self.engine self.engine
.batch_modeling_cmd( .batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
SourceRange::default(), SourceRange::default(),
&ModelingCmd::from(mcmd::SetSceneUnits { &ModelingCmd::from(mcmd::SetSceneUnits {
unit: match self.settings.units { unit: match self.settings.units {
@ -1876,15 +1999,7 @@ impl ExecutorContext {
}), }),
) )
.await?; .await?;
let memory = if let Some(memory) = memory {
memory.clone()
} else {
Default::default()
};
let mut exec_state = ExecState {
memory,
..Default::default()
};
self.inner_execute(program, &mut exec_state, crate::executor::BodyType::Root) self.inner_execute(program, &mut exec_state, crate::executor::BodyType::Root)
.await?; .await?;
let session_data = self.engine.get_session_data(); let session_data = self.engine.get_session_data();
@ -2029,8 +2144,12 @@ impl ExecutorContext {
} }
/// Execute the program, then get a PNG screenshot. /// Execute the program, then get a PNG screenshot.
pub async fn execute_and_prepare_snapshot(&self, program: &Program) -> Result<TakeSnapshot> { pub async fn execute_and_prepare_snapshot(
let _ = self.run(program, None).await?; &self,
program: &Program,
id_generator: IdGenerator,
) -> Result<TakeSnapshot> {
let _ = self.run(program, None, id_generator).await?;
// Zoom to fit. // Zoom to fit.
self.engine self.engine
@ -2175,7 +2294,7 @@ mod tests {
settings: Default::default(), settings: Default::default(),
context_type: ContextType::Mock, context_type: ContextType::Mock,
}; };
let exec_state = ctx.run(&program, None).await?; let exec_state = ctx.run(&program, None, IdGenerator::default()).await?;
Ok(exec_state.memory) Ok(exec_state.memory)
} }

View File

@ -41,7 +41,7 @@ use tower_lsp::{
use crate::{ use crate::{
ast::types::{Expr, VariableKind}, ast::types::{Expr, VariableKind},
executor::SourceRange, executor::{IdGenerator, SourceRange},
lsp::{backend::Backend as _, util::IntoDiagnostic}, lsp::{backend::Backend as _, util::IntoDiagnostic},
parser::PIPE_OPERATOR, parser::PIPE_OPERATOR,
token::TokenType, token::TokenType,
@ -588,10 +588,15 @@ impl Backend {
return Ok(()); return Ok(());
} }
// Clear the scene, before we execute so it's not fugly as shit. let mut id_generator = IdGenerator::default();
executor_ctx.engine.clear_scene(SourceRange::default()).await?;
let exec_state = match executor_ctx.run(ast, None).await { // Clear the scene, before we execute so it's not fugly as shit.
executor_ctx
.engine
.clear_scene(&mut id_generator, SourceRange::default())
.await?;
let exec_state = match executor_ctx.run(ast, None, id_generator).await {
Ok(exec_state) => exec_state, Ok(exec_state) => exec_state,
Err(err) => { Err(err) => {
self.memory_map.remove(params.uri.as_str()); self.memory_map.remove(params.uri.as_str());

View File

@ -133,7 +133,7 @@ async fn inner_chamfer(
EdgeReference::Tag(edge_tag) => args.get_tag_engine_info(exec_state, &edge_tag)?.id, EdgeReference::Tag(edge_tag) => args.get_tag_engine_info(exec_state, &edge_tag)?.id,
}; };
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_end_cmd( args.batch_end_cmd(
id, id,
ModelingCmd::from(mcmd::Solid3dFilletEdge { ModelingCmd::from(mcmd::Solid3dFilletEdge {

View File

@ -18,10 +18,10 @@ use crate::{
}; };
/// Extrudes by a given amount. /// Extrudes by a given amount.
pub async fn extrude(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (length, sketch_set) = args.get_number_sketch_set()?; let (length, sketch_set) = args.get_number_sketch_set()?;
let result = inner_extrude(length, sketch_set, args).await?; let result = inner_extrude(length, sketch_set, exec_state, args).await?;
Ok(result.into()) Ok(result.into())
} }
@ -75,8 +75,13 @@ pub async fn extrude(_exec_state: &mut ExecState, args: Args) -> Result<KclValue
#[stdlib { #[stdlib {
name = "extrude" name = "extrude"
}] }]
async fn inner_extrude(length: f64, sketch_set: SketchSet, args: Args) -> Result<SolidSet, KclError> { async fn inner_extrude(
let id = uuid::Uuid::new_v4(); length: f64,
sketch_set: SketchSet,
exec_state: &mut ExecState,
args: Args,
) -> Result<SolidSet, KclError> {
let id = exec_state.id_generator.next_uuid();
// Extrude the element(s). // Extrude the element(s).
let sketches: Vec<Sketch> = sketch_set.into(); let sketches: Vec<Sketch> = sketch_set.into();
@ -85,7 +90,7 @@ async fn inner_extrude(length: f64, sketch_set: SketchSet, args: Args) -> Result
// Before we extrude, we need to enable the sketch mode. // Before we extrude, we need to enable the sketch mode.
// We do this here in case extrude is called out of order. // We do this here in case extrude is called out of order.
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::EnableSketchMode { ModelingCmd::from(mcmd::EnableSketchMode {
animated: false, animated: false,
ortho: false, ortho: false,
@ -112,21 +117,26 @@ async fn inner_extrude(length: f64, sketch_set: SketchSet, args: Args) -> Result
// Disable the sketch mode. // Disable the sketch mode.
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}), ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
) )
.await?; .await?;
solids.push(do_post_extrude(sketch.clone(), length, args.clone()).await?); solids.push(do_post_extrude(sketch.clone(), length, exec_state, args.clone()).await?);
} }
Ok(solids.into()) Ok(solids.into())
} }
pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) -> Result<Box<Solid>, KclError> { pub(crate) async fn do_post_extrude(
sketch: Sketch,
length: f64,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
// Bring the object to the front of the scene. // Bring the object to the front of the scene.
// See: https://github.com/KittyCAD/modeling-app/issues/806 // See: https://github.com/KittyCAD/modeling-app/issues/806
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }), ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }),
) )
.await?; .await?;
@ -159,7 +169,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
let solid3d_info = args let solid3d_info = args
.send_modeling_cmd( .send_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo { ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
edge_id, edge_id,
object_id: sketch.id, object_id: sketch.id,
@ -192,7 +202,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
// Instead, the Typescript codebases (which handles WebSocket sends when compiled via Wasm) // Instead, the Typescript codebases (which handles WebSocket sends when compiled via Wasm)
// uses this to build the artifact graph, which the UI needs. // uses this to build the artifact graph, which the UI needs.
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge { ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
edge_id: curve_id, edge_id: curve_id,
object_id: sketch.id, object_id: sketch.id,
@ -202,7 +212,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
.await?; .await?;
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge { ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
edge_id: curve_id, edge_id: curve_id,
object_id: sketch.id, object_id: sketch.id,
@ -216,7 +226,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
sides: face_id_map, sides: face_id_map,
start_cap_id, start_cap_id,
end_cap_id, end_cap_id,
} = analyze_faces(&args, face_infos); } = analyze_faces(exec_state, &args, face_infos);
// Iterate over the sketch.value array and add face_id to GeoMeta // Iterate over the sketch.value array and add face_id to GeoMeta
let new_value = sketch let new_value = sketch
.value .value
@ -252,7 +262,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane { let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
// pushing this values with a fake face_id to make extrudes mock-execute safe // pushing this values with a fake face_id to make extrudes mock-execute safe
face_id: Uuid::new_v4(), face_id: exec_state.id_generator.next_uuid(),
tag: path.get_base().tag.clone(), tag: path.get_base().tag.clone(),
geo_meta: GeoMeta { geo_meta: GeoMeta {
id: path.get_base().geo_meta.id, id: path.get_base().geo_meta.id,
@ -291,15 +301,15 @@ struct Faces {
start_cap_id: Option<Uuid>, start_cap_id: Option<Uuid>,
} }
fn analyze_faces(args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces { fn analyze_faces(exec_state: &mut ExecState, args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
let mut faces = Faces { let mut faces = Faces {
sides: HashMap::with_capacity(face_infos.len()), sides: HashMap::with_capacity(face_infos.len()),
..Default::default() ..Default::default()
}; };
if args.ctx.is_mock() { if args.ctx.is_mock() {
// Create fake IDs for start and end caps, to make extrudes mock-execute safe // Create fake IDs for start and end caps, to make extrudes mock-execute safe
faces.start_cap_id = Some(Uuid::new_v4()); faces.start_cap_id = Some(exec_state.id_generator.next_uuid());
faces.end_cap_id = Some(Uuid::new_v4()); faces.end_cap_id = Some(exec_state.id_generator.next_uuid());
} }
for face_info in face_infos { for face_info in face_infos {
match face_info.cap { match face_info.cap {

View File

@ -142,7 +142,7 @@ async fn inner_fillet(
for edge_tag in data.tags { for edge_tag in data.tags {
let edge_id = edge_tag.get_engine_id(exec_state, &args)?; let edge_id = edge_tag.get_engine_id(exec_state, &args)?;
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_end_cmd( args.batch_end_cmd(
id, id,
ModelingCmd::from(mcmd::Solid3dFilletEdge { ModelingCmd::from(mcmd::Solid3dFilletEdge {
@ -229,15 +229,16 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
}] }]
async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> { async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> {
if args.ctx.is_mock() { if args.ctx.is_mock() {
return Ok(Uuid::new_v4()); return Ok(exec_state.id_generator.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
let id = exec_state.id_generator.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
uuid::Uuid::new_v4(), id,
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge { ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
edge_id: tagged_path.id, edge_id: tagged_path.id,
object_id: tagged_path.sketch, object_id: tagged_path.sketch,
@ -310,15 +311,16 @@ async fn inner_get_next_adjacent_edge(
args: Args, args: Args,
) -> Result<Uuid, KclError> { ) -> Result<Uuid, KclError> {
if args.ctx.is_mock() { if args.ctx.is_mock() {
return Ok(Uuid::new_v4()); return Ok(exec_state.id_generator.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
let id = exec_state.id_generator.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
uuid::Uuid::new_v4(), id,
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge { ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
edge_id: tagged_path.id, edge_id: tagged_path.id,
object_id: tagged_path.sketch, object_id: tagged_path.sketch,
@ -399,15 +401,16 @@ async fn inner_get_previous_adjacent_edge(
args: Args, args: Args,
) -> Result<Uuid, KclError> { ) -> Result<Uuid, KclError> {
if args.ctx.is_mock() { if args.ctx.is_mock() {
return Ok(Uuid::new_v4()); return Ok(exec_state.id_generator.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
let id = exec_state.id_generator.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
uuid::Uuid::new_v4(), id,
ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge { ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge {
edge_id: tagged_path.id, edge_id: tagged_path.id,
object_id: tagged_path.sketch, object_id: tagged_path.sketch,

View File

@ -32,10 +32,10 @@ pub struct HelixData {
} }
/// Create a helix on a cylinder. /// Create a helix on a cylinder.
pub async fn helix(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, solid): (HelixData, Box<Solid>) = args.get_data_and_solid()?; let (data, solid): (HelixData, Box<Solid>) = args.get_data_and_solid()?;
let solid = inner_helix(data, solid, args).await?; let solid = inner_helix(data, solid, exec_state, args).await?;
Ok(KclValue::Solid(solid)) Ok(KclValue::Solid(solid))
} }
@ -54,8 +54,13 @@ pub async fn helix(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib { #[stdlib {
name = "helix", name = "helix",
}] }]
async fn inner_helix(data: HelixData, solid: Box<Solid>, args: Args) -> Result<Box<Solid>, KclError> { async fn inner_helix(
let id = uuid::Uuid::new_v4(); data: HelixData,
solid: Box<Solid>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
ModelingCmd::from(mcmd::EntityMakeHelix { ModelingCmd::from(mcmd::EntityMakeHelix {

View File

@ -126,10 +126,10 @@ impl From<ImportFormat> for InputFormat {
/// ///
/// Import paths are relative to the current project directory. This only works in the desktop app /// Import paths are relative to the current project directory. This only works in the desktop app
/// not in browser. /// not in browser.
pub async fn import(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (file_path, options): (String, Option<ImportFormat>) = args.get_import_data()?; let (file_path, options): (String, Option<ImportFormat>) = args.get_import_data()?;
let imported_geometry = inner_import(file_path, options, args).await?; let imported_geometry = inner_import(file_path, options, exec_state, args).await?;
Ok(KclValue::ImportedGeometry(imported_geometry)) Ok(KclValue::ImportedGeometry(imported_geometry))
} }
@ -170,6 +170,7 @@ pub async fn import(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
async fn inner_import( async fn inner_import(
file_path: String, file_path: String,
options: Option<ImportFormat>, options: Option<ImportFormat>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<ImportedGeometry, KclError> { ) -> Result<ImportedGeometry, KclError> {
if file_path.is_empty() { if file_path.is_empty() {
@ -286,13 +287,13 @@ async fn inner_import(
if args.ctx.is_mock() { if args.ctx.is_mock() {
return Ok(ImportedGeometry { return Ok(ImportedGeometry {
id: uuid::Uuid::new_v4(), id: exec_state.id_generator.next_uuid(),
value: import_files.iter().map(|f| f.path.to_string()).collect(), value: import_files.iter().map(|f| f.path.to_string()).collect(),
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
}); });
} }
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
id, id,

View File

@ -50,10 +50,10 @@ impl Default for LoftData {
} }
/// Create a 3D surface or solid by interpolating between two or more sketches. /// Create a 3D surface or solid by interpolating between two or more sketches.
pub async fn loft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (sketches, data): (Vec<Sketch>, Option<LoftData>) = args.get_sketches_and_data()?; let (sketches, data): (Vec<Sketch>, Option<LoftData>) = args.get_sketches_and_data()?;
let solid = inner_loft(sketches, data, args).await?; let solid = inner_loft(sketches, data, exec_state, args).await?;
Ok(KclValue::Solid(solid)) Ok(KclValue::Solid(solid))
} }
@ -135,7 +135,12 @@ pub async fn loft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
#[stdlib { #[stdlib {
name = "loft", name = "loft",
}] }]
async fn inner_loft(sketches: Vec<Sketch>, data: Option<LoftData>, args: Args) -> Result<Box<Solid>, KclError> { async fn inner_loft(
sketches: Vec<Sketch>,
data: Option<LoftData>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
// Make sure we have at least two sketches. // Make sure we have at least two sketches.
if sketches.len() < 2 { if sketches.len() < 2 {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
@ -150,7 +155,7 @@ async fn inner_loft(sketches: Vec<Sketch>, data: Option<LoftData>, args: Args) -
// Get the loft data. // Get the loft data.
let data = data.unwrap_or_default(); let data = data.unwrap_or_default();
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
ModelingCmd::from(mcmd::Loft { ModelingCmd::from(mcmd::Loft {
@ -166,5 +171,5 @@ async fn inner_loft(sketches: Vec<Sketch>, data: Option<LoftData>, args: Args) -
.await?; .await?;
// Using the first sketch as the base curve, idk we might want to change this later. // Using the first sketch as the base curve, idk we might want to change this later.
do_post_extrude(sketches[0].clone(), 0.0, args).await do_post_extrude(sketches[0].clone(), 0.0, exec_state, args).await
} }

View File

@ -121,7 +121,7 @@ async fn inner_mirror_2d(
let (axis, origin) = axis.axis_and_origin()?; let (axis, origin) = axis.axis_and_origin()?;
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::EntityMirror { ModelingCmd::from(mcmd::EntityMirror {
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(), ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
axis, axis,
@ -134,7 +134,7 @@ async fn inner_mirror_2d(
let edge_id = edge.get_engine_id(exec_state, &args)?; let edge_id = edge.get_engine_id(exec_state, &args)?;
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::EntityMirrorAcrossEdge { ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(), ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
edge_id, edge_id,

View File

@ -296,7 +296,7 @@ async fn inner_pattern_transform<'a>(
let mut solids = Vec::new(); let mut solids = Vec::new();
for e in starting_solids { for e in starting_solids {
let new_solids = send_pattern_transform(transform.clone(), &e, args).await?; let new_solids = send_pattern_transform(transform.clone(), &e, exec_state, args).await?;
solids.extend(new_solids); solids.extend(new_solids);
} }
Ok(solids) Ok(solids)
@ -307,9 +307,10 @@ async fn send_pattern_transform(
// https://github.com/KittyCAD/modeling-app/issues/2821 // https://github.com/KittyCAD/modeling-app/issues/2821
transform: Vec<Transform>, transform: Vec<Transform>,
solid: &Solid, solid: &Solid,
exec_state: &mut ExecState,
args: &Args, args: &Args,
) -> Result<Vec<Box<Solid>>, KclError> { ) -> Result<Vec<Box<Solid>>, KclError> {
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
@ -473,7 +474,7 @@ mod tests {
} }
/// A linear pattern on a 2D sketch. /// A linear pattern on a 2D sketch.
pub async fn pattern_linear_2d(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch_set): (LinearPattern2dData, SketchSet) = args.get_data_and_sketch_set()?; let (data, sketch_set): (LinearPattern2dData, SketchSet) = args.get_data_and_sketch_set()?;
if data.axis == [0.0, 0.0] { if data.axis == [0.0, 0.0] {
@ -485,7 +486,7 @@ pub async fn pattern_linear_2d(_exec_state: &mut ExecState, args: Args) -> Resul
})); }));
} }
let sketches = inner_pattern_linear_2d(data, sketch_set, args).await?; let sketches = inner_pattern_linear_2d(data, sketch_set, exec_state, args).await?;
Ok(sketches.into()) Ok(sketches.into())
} }
@ -509,6 +510,7 @@ pub async fn pattern_linear_2d(_exec_state: &mut ExecState, args: Args) -> Resul
async fn inner_pattern_linear_2d( async fn inner_pattern_linear_2d(
data: LinearPattern2dData, data: LinearPattern2dData,
sketch_set: SketchSet, sketch_set: SketchSet,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Sketch>>, KclError> { ) -> Result<Vec<Box<Sketch>>, KclError> {
let starting_sketches: Vec<Box<Sketch>> = sketch_set.into(); let starting_sketches: Vec<Box<Sketch>> = sketch_set.into();
@ -522,6 +524,7 @@ async fn inner_pattern_linear_2d(
let geometries = pattern_linear( let geometries = pattern_linear(
LinearPattern::TwoD(data.clone()), LinearPattern::TwoD(data.clone()),
Geometry::Sketch(sketch.clone()), Geometry::Sketch(sketch.clone()),
exec_state,
args.clone(), args.clone(),
) )
.await?; .await?;
@ -600,6 +603,7 @@ async fn inner_pattern_linear_3d(
let geometries = pattern_linear( let geometries = pattern_linear(
LinearPattern::ThreeD(data.clone()), LinearPattern::ThreeD(data.clone()),
Geometry::Solid(solid.clone()), Geometry::Solid(solid.clone()),
exec_state,
args.clone(), args.clone(),
) )
.await?; .await?;
@ -617,8 +621,13 @@ async fn inner_pattern_linear_3d(
Ok(solids) Ok(solids)
} }
async fn pattern_linear(data: LinearPattern, geometry: Geometry, args: Args) -> Result<Geometries, KclError> { async fn pattern_linear(
let id = uuid::Uuid::new_v4(); data: LinearPattern,
geometry: Geometry,
exec_state: &mut ExecState,
args: Args,
) -> Result<Geometries, KclError> {
let id = exec_state.id_generator.next_uuid();
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
@ -745,10 +754,10 @@ impl CircularPattern {
} }
/// A circular pattern on a 2D sketch. /// A circular pattern on a 2D sketch.
pub async fn pattern_circular_2d(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch_set): (CircularPattern2dData, SketchSet) = args.get_data_and_sketch_set()?; let (data, sketch_set): (CircularPattern2dData, SketchSet) = args.get_data_and_sketch_set()?;
let sketches = inner_pattern_circular_2d(data, sketch_set, args).await?; let sketches = inner_pattern_circular_2d(data, sketch_set, exec_state, args).await?;
Ok(sketches.into()) Ok(sketches.into())
} }
@ -779,6 +788,7 @@ pub async fn pattern_circular_2d(_exec_state: &mut ExecState, args: Args) -> Res
async fn inner_pattern_circular_2d( async fn inner_pattern_circular_2d(
data: CircularPattern2dData, data: CircularPattern2dData,
sketch_set: SketchSet, sketch_set: SketchSet,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Sketch>>, KclError> { ) -> Result<Vec<Box<Sketch>>, KclError> {
let starting_sketches: Vec<Box<Sketch>> = sketch_set.into(); let starting_sketches: Vec<Box<Sketch>> = sketch_set.into();
@ -792,6 +802,7 @@ async fn inner_pattern_circular_2d(
let geometries = pattern_circular( let geometries = pattern_circular(
CircularPattern::TwoD(data.clone()), CircularPattern::TwoD(data.clone()),
Geometry::Sketch(sketch.clone()), Geometry::Sketch(sketch.clone()),
exec_state,
args.clone(), args.clone(),
) )
.await?; .await?;
@ -861,6 +872,7 @@ async fn inner_pattern_circular_3d(
let geometries = pattern_circular( let geometries = pattern_circular(
CircularPattern::ThreeD(data.clone()), CircularPattern::ThreeD(data.clone()),
Geometry::Solid(solid.clone()), Geometry::Solid(solid.clone()),
exec_state,
args.clone(), args.clone(),
) )
.await?; .await?;
@ -878,8 +890,13 @@ async fn inner_pattern_circular_3d(
Ok(solids) Ok(solids)
} }
async fn pattern_circular(data: CircularPattern, geometry: Geometry, args: Args) -> Result<Geometries, KclError> { async fn pattern_circular(
let id = uuid::Uuid::new_v4(); data: CircularPattern,
geometry: Geometry,
exec_state: &mut ExecState,
args: Args,
) -> Result<Geometries, KclError> {
let id = exec_state.id_generator.next_uuid();
let center = data.center(); let center = data.center();
let resp = args let resp = args

View File

@ -48,10 +48,10 @@ impl From<StandardPlane> for PlaneData {
} }
/// Offset a plane by a distance along its normal. /// Offset a plane by a distance along its normal.
pub async fn offset_plane(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (std_plane, offset): (StandardPlane, f64) = args.get_data_and_float()?; let (std_plane, offset): (StandardPlane, f64) = args.get_data_and_float()?;
let plane = inner_offset_plane(std_plane, offset).await?; let plane = inner_offset_plane(std_plane, offset, exec_state).await?;
Ok(KclValue::UserVal(UserVal::new( Ok(KclValue::UserVal(UserVal::new(
vec![Metadata { vec![Metadata {
@ -132,11 +132,15 @@ pub async fn offset_plane(_exec_state: &mut ExecState, args: Args) -> Result<Kcl
#[stdlib { #[stdlib {
name = "offsetPlane", name = "offsetPlane",
}] }]
async fn inner_offset_plane(std_plane: StandardPlane, offset: f64) -> Result<PlaneData, KclError> { async fn inner_offset_plane(
std_plane: StandardPlane,
offset: f64,
exec_state: &mut ExecState,
) -> Result<PlaneData, KclError> {
// Convert to the plane type. // Convert to the plane type.
let plane_data: PlaneData = std_plane.into(); let plane_data: PlaneData = std_plane.into();
// Convert to a plane. // Convert to a plane.
let mut plane = Plane::from(plane_data); let mut plane = Plane::from_plane_data(plane_data, exec_state);
match std_plane { match std_plane {
StandardPlane::XY => { StandardPlane::XY => {

View File

@ -263,7 +263,7 @@ async fn inner_revolve(
let angle = Angle::from_degrees(data.angle.unwrap_or(360.0)); let angle = Angle::from_degrees(data.angle.unwrap_or(360.0));
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
match data.axis { match data.axis {
AxisOrEdgeReference::Axis(axis) => { AxisOrEdgeReference::Axis(axis) => {
let (axis, origin) = axis.axis_and_origin()?; let (axis, origin) = axis.axis_and_origin()?;
@ -295,7 +295,7 @@ async fn inner_revolve(
} }
} }
do_post_extrude(sketch, 0.0, args).await do_post_extrude(sketch, 0.0, exec_state, args).await
} }
#[cfg(test)] #[cfg(test)]

View File

@ -97,7 +97,7 @@ async fn inner_circle(
let angle_start = Angle::zero(); let angle_start = Angle::zero();
let angle_end = Angle::turn(); let angle_end = Angle::turn();
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,

View File

@ -229,7 +229,7 @@ async fn inner_shell(
} }
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::Solid3dShellFace { ModelingCmd::from(mcmd::Solid3dShellFace {
hollow: false, hollow: false,
face_ids, face_ids,
@ -314,7 +314,7 @@ async fn inner_hollow(
args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?; args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?;
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::Solid3dShellFace { ModelingCmd::from(mcmd::Solid3dShellFace {
hollow: true, hollow: true,
face_ids: Vec::new(), // This is empty because we want to hollow the entire object. face_ids: Vec::new(), // This is empty because we want to hollow the entire object.

View File

@ -16,8 +16,8 @@ use crate::{
ast::types::TagDeclarator, ast::types::TagDeclarator,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ executor::{
BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, PlaneType, Point2d, Point3d, Sketch, SketchSet, BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface,
SketchSurface, Solid, TagEngineInfo, TagIdentifier, UserVal, Solid, TagEngineInfo, TagIdentifier, UserVal,
}, },
std::{ std::{
utils::{ utils::{
@ -93,10 +93,10 @@ pub enum StartOrEnd {
} }
/// Draw a line to a point. /// Draw a line to a point.
pub async fn line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (to, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (to, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_line_to(to, sketch, tag, args).await?; let new_sketch = inner_line_to(to, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -119,10 +119,11 @@ async fn inner_line_to(
to: [f64; 2], to: [f64; 2],
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -159,10 +160,10 @@ async fn inner_line_to(
} }
/// Draw a line to a point on the x-axis. /// Draw a line to a point on the x-axis.
pub async fn x_line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn x_line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (to, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (to, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_x_line_to(to, sketch, tag, args).await?; let new_sketch = inner_x_line_to(to, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -192,19 +193,25 @@ pub async fn x_line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclVal
#[stdlib { #[stdlib {
name = "xLineTo", name = "xLineTo",
}] }]
async fn inner_x_line_to(to: f64, sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> { async fn inner_x_line_to(
to: f64,
sketch: Sketch,
tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let new_sketch = inner_line_to([to, from.y], sketch, tag, args).await?; let new_sketch = inner_line_to([to, from.y], sketch, tag, exec_state, args).await?;
Ok(new_sketch) Ok(new_sketch)
} }
/// Draw a line to a point on the y-axis. /// Draw a line to a point on the y-axis.
pub async fn y_line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn y_line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (to, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (to, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_y_line_to(to, sketch, tag, args).await?; let new_sketch = inner_y_line_to(to, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -227,18 +234,24 @@ pub async fn y_line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclVal
#[stdlib { #[stdlib {
name = "yLineTo", name = "yLineTo",
}] }]
async fn inner_y_line_to(to: f64, sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> { async fn inner_y_line_to(
to: f64,
sketch: Sketch,
tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let new_sketch = inner_line_to([from.x, to], sketch, tag, args).await?; let new_sketch = inner_line_to([from.x, to], sketch, tag, exec_state, args).await?;
Ok(new_sketch) Ok(new_sketch)
} }
/// Draw a line. /// Draw a line.
pub async fn line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_line(delta, sketch, tag, args).await?; let new_sketch = inner_line(delta, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -273,12 +286,13 @@ async fn inner_line(
delta: [f64; 2], delta: [f64; 2],
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let to = [from.x + delta[0], from.y + delta[1]]; let to = [from.x + delta[0], from.y + delta[1]];
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -315,10 +329,10 @@ async fn inner_line(
} }
/// Draw a line on the x-axis. /// Draw a line on the x-axis.
pub async fn x_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn x_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (length, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (length, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_x_line(length, sketch, tag, args).await?; let new_sketch = inner_x_line(length, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -347,15 +361,21 @@ pub async fn x_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib { #[stdlib {
name = "xLine", name = "xLine",
}] }]
async fn inner_x_line(length: f64, sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> { async fn inner_x_line(
inner_line([length, 0.0], sketch, tag, args).await length: f64,
sketch: Sketch,
tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
inner_line([length, 0.0], sketch, tag, exec_state, args).await
} }
/// Draw a line on the y-axis. /// Draw a line on the y-axis.
pub async fn y_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn y_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (length, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (length, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_y_line(length, sketch, tag, args).await?; let new_sketch = inner_y_line(length, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -379,8 +399,14 @@ pub async fn y_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib { #[stdlib {
name = "yLine", name = "yLine",
}] }]
async fn inner_y_line(length: f64, sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> { async fn inner_y_line(
inner_line([0.0, length], sketch, tag, args).await length: f64,
sketch: Sketch,
tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
inner_line([0.0, length], sketch, tag, exec_state, args).await
} }
/// Data to draw an angled line. /// Data to draw an angled line.
@ -400,10 +426,10 @@ pub enum AngledLineData {
} }
/// Draw an angled line. /// Draw an angled line.
pub async fn angled_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (AngledLineData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (AngledLineData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line(data, sketch, tag, args).await?; let new_sketch = inner_angled_line(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -431,6 +457,7 @@ async fn inner_angled_line(
data: AngledLineData, data: AngledLineData,
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
@ -448,7 +475,7 @@ async fn inner_angled_line(
let to: [f64; 2] = [from.x + delta[0], from.y + delta[1]]; let to: [f64; 2] = [from.x + delta[0], from.y + delta[1]];
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -484,10 +511,10 @@ async fn inner_angled_line(
} }
/// Draw an angled line of a given x length. /// Draw an angled line of a given x length.
pub async fn angled_line_of_x_length(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn angled_line_of_x_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (AngledLineData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (AngledLineData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line_of_x_length(data, sketch, tag, args).await?; let new_sketch = inner_angled_line_of_x_length(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -511,6 +538,7 @@ async fn inner_angled_line_of_x_length(
data: AngledLineData, data: AngledLineData,
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let (angle, length) = match data { let (angle, length) = match data {
@ -534,7 +562,7 @@ async fn inner_angled_line_of_x_length(
let to = get_y_component(Angle::from_degrees(angle), length); let to = get_y_component(Angle::from_degrees(angle), length);
let new_sketch = inner_line(to.into(), sketch, tag, args).await?; let new_sketch = inner_line(to.into(), sketch, tag, exec_state, args).await?;
Ok(new_sketch) Ok(new_sketch)
} }
@ -551,10 +579,10 @@ pub struct AngledLineToData {
} }
/// Draw an angled line to a given x coordinate. /// Draw an angled line to a given x coordinate.
pub async fn angled_line_to_x(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn angled_line_to_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line_to_x(data, sketch, tag, args).await?; let new_sketch = inner_angled_line_to_x(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -579,6 +607,7 @@ async fn inner_angled_line_to_x(
data: AngledLineToData, data: AngledLineToData,
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
@ -602,15 +631,15 @@ async fn inner_angled_line_to_x(
let y_component = x_component * f64::tan(angle.to_radians()); let y_component = x_component * f64::tan(angle.to_radians());
let y_to = from.y + y_component; let y_to = from.y + y_component;
let new_sketch = inner_line_to([x_to, y_to], sketch, tag, args).await?; let new_sketch = inner_line_to([x_to, y_to], sketch, tag, exec_state, args).await?;
Ok(new_sketch) Ok(new_sketch)
} }
/// Draw an angled line of a given y length. /// Draw an angled line of a given y length.
pub async fn angled_line_of_y_length(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn angled_line_of_y_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (AngledLineData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (AngledLineData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line_of_y_length(data, sketch, tag, args).await?; let new_sketch = inner_angled_line_of_y_length(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -637,6 +666,7 @@ async fn inner_angled_line_of_y_length(
data: AngledLineData, data: AngledLineData,
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let (angle, length) = match data { let (angle, length) = match data {
@ -660,16 +690,16 @@ async fn inner_angled_line_of_y_length(
let to = get_x_component(Angle::from_degrees(angle), length); let to = get_x_component(Angle::from_degrees(angle), length);
let new_sketch = inner_line(to.into(), sketch, tag, args).await?; let new_sketch = inner_line(to.into(), sketch, tag, exec_state, args).await?;
Ok(new_sketch) Ok(new_sketch)
} }
/// Draw an angled line to a given y coordinate. /// Draw an angled line to a given y coordinate.
pub async fn angled_line_to_y(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn angled_line_to_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line_to_y(data, sketch, tag, args).await?; let new_sketch = inner_angled_line_to_y(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -694,6 +724,7 @@ async fn inner_angled_line_to_y(
data: AngledLineToData, data: AngledLineToData,
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
@ -717,7 +748,7 @@ async fn inner_angled_line_to_y(
let x_component = y_component / f64::tan(angle.to_radians()); let x_component = y_component / f64::tan(angle.to_radians());
let x_to = from.x + x_component; let x_to = from.x + x_component;
let new_sketch = inner_line_to([x_to, y_to], sketch, tag, args).await?; let new_sketch = inner_line_to([x_to, y_to], sketch, tag, exec_state, args).await?;
Ok(new_sketch) Ok(new_sketch)
} }
@ -788,7 +819,7 @@ async fn inner_angled_line_that_intersects(
from, from,
); );
let new_sketch = inner_line_to(to.into(), sketch, tag, args).await?; let new_sketch = inner_line_to(to.into(), sketch, tag, exec_state, args).await?;
Ok(new_sketch) Ok(new_sketch)
} }
@ -891,82 +922,6 @@ pub enum PlaneData {
}, },
} }
impl From<PlaneData> for Plane {
fn from(value: PlaneData) -> Self {
let id = uuid::Uuid::new_v4();
match value {
PlaneData::XY => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(1.0, 0.0, 0.0),
y_axis: Point3d::new(0.0, 1.0, 0.0),
z_axis: Point3d::new(0.0, 0.0, 1.0),
value: PlaneType::XY,
meta: vec![],
},
PlaneData::NegXY => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(1.0, 0.0, 0.0),
y_axis: Point3d::new(0.0, 1.0, 0.0),
z_axis: Point3d::new(0.0, 0.0, -1.0),
value: PlaneType::XY,
meta: vec![],
},
PlaneData::XZ => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(1.0, 0.0, 0.0),
y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(0.0, -1.0, 0.0),
value: PlaneType::XZ,
meta: vec![],
},
PlaneData::NegXZ => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(-1.0, 0.0, 0.0),
y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(0.0, 1.0, 0.0),
value: PlaneType::XZ,
meta: vec![],
},
PlaneData::YZ => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(0.0, 1.0, 0.0),
y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(1.0, 0.0, 0.0),
value: PlaneType::YZ,
meta: vec![],
},
PlaneData::NegYZ => Plane {
id,
origin: Point3d::new(0.0, 0.0, 0.0),
x_axis: Point3d::new(0.0, 1.0, 0.0),
y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(-1.0, 0.0, 0.0),
value: PlaneType::YZ,
meta: vec![],
},
PlaneData::Plane {
origin,
x_axis,
y_axis,
z_axis,
} => Plane {
id,
origin: *origin,
x_axis: *x_axis,
y_axis: *y_axis,
z_axis: *z_axis,
value: PlaneType::Custom,
meta: vec![],
},
}
}
}
/// Start a sketch on a specific plane or face. /// Start a sketch on a specific plane or face.
pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, tag): (SketchData, Option<FaceTag>) = args.get_data_and_optional_tag()?; let (data, tag): (SketchData, Option<FaceTag>) = args.get_data_and_optional_tag()?;
@ -1089,7 +1044,7 @@ async fn inner_start_sketch_on(
) -> Result<SketchSurface, KclError> { ) -> Result<SketchSurface, KclError> {
match data { match data {
SketchData::Plane(plane_data) => { SketchData::Plane(plane_data) => {
let plane = start_sketch_on_plane(plane_data, args).await?; let plane = start_sketch_on_plane(plane_data, exec_state, args).await?;
Ok(SketchSurface::Plane(plane)) Ok(SketchSurface::Plane(plane))
} }
SketchData::Solid(solid) => { SketchData::Solid(solid) => {
@ -1125,11 +1080,19 @@ async fn start_sketch_on_face(
})) }))
} }
async fn start_sketch_on_plane(data: PlaneData, args: &Args) -> Result<Box<Plane>, KclError> { async fn start_sketch_on_plane(
let mut plane: Plane = data.clone().into(); data: PlaneData,
exec_state: &mut ExecState,
args: &Args,
) -> Result<Box<Plane>, KclError> {
let mut plane = Plane::from_plane_data(data.clone(), exec_state);
// Get the default planes. // Get the default planes.
let default_planes = args.ctx.engine.default_planes(args.source_range).await?; let default_planes = args
.ctx
.engine
.default_planes(&mut exec_state.id_generator, args.source_range)
.await?;
plane.id = match data { plane.id = match data {
PlaneData::XY => default_planes.xy, PlaneData::XY => default_planes.xy,
@ -1145,7 +1108,7 @@ async fn start_sketch_on_plane(data: PlaneData, args: &Args) -> Result<Box<Plane
z_axis: _, z_axis: _,
} => { } => {
// Create the custom plane on the fly. // Create the custom plane on the fly.
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
ModelingCmd::from(mcmd::MakePlane { ModelingCmd::from(mcmd::MakePlane {
@ -1228,7 +1191,7 @@ pub(crate) async fn inner_start_profile_at(
// Enter sketch mode on the surface. // Enter sketch mode on the surface.
// We call this here so you can reuse the sketch surface for multiple sketches. // We call this here so you can reuse the sketch surface for multiple sketches.
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
ModelingCmd::from(mcmd::EnableSketchMode { ModelingCmd::from(mcmd::EnableSketchMode {
@ -1246,8 +1209,8 @@ pub(crate) async fn inner_start_profile_at(
) )
.await?; .await?;
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
let path_id = uuid::Uuid::new_v4(); let path_id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd(path_id, ModelingCmd::from(mcmd::StartPath {})) args.batch_modeling_cmd(path_id, ModelingCmd::from(mcmd::StartPath {}))
.await?; .await?;
@ -1375,10 +1338,10 @@ pub(crate) fn inner_profile_start(sketch: Sketch) -> Result<[f64; 2], KclError>
} }
/// Close the current sketch. /// Close the current sketch.
pub async fn close(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (sketch, tag): (Sketch, Option<TagDeclarator>) = args.get_sketch_and_optional_tag()?; let (sketch, tag): (Sketch, Option<TagDeclarator>) = args.get_sketch_and_optional_tag()?;
let new_sketch = inner_close(sketch, tag, args).await?; let new_sketch = inner_close(sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -1407,11 +1370,16 @@ pub async fn close(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib { #[stdlib {
name = "close", name = "close",
}] }]
pub(crate) async fn inner_close(sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> { pub(crate) async fn inner_close(
sketch: Sketch,
tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let to: Point2d = sketch.start.from.into(); let to: Point2d = sketch.start.from.into();
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id })) args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }))
.await?; .await?;
@ -1420,7 +1388,7 @@ pub(crate) async fn inner_close(sketch: Sketch, tag: Option<TagDeclarator>, args
if let SketchSurface::Plane(_) = sketch.on { if let SketchSurface::Plane(_) = sketch.on {
// We were on a plane, disable the sketch mode. // We were on a plane, disable the sketch mode.
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}), ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
) )
.await?; .await?;
@ -1478,10 +1446,10 @@ pub enum ArcData {
} }
/// Draw an arc. /// Draw an arc.
pub async fn arc(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (ArcData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (ArcData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_arc(data, sketch, tag, args).await?; let new_sketch = inner_arc(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -1514,6 +1482,7 @@ pub(crate) async fn inner_arc(
data: ArcData, data: ArcData,
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from: Point2d = sketch.current_pen_position()?; let from: Point2d = sketch.current_pen_position()?;
@ -1542,7 +1511,7 @@ pub(crate) async fn inner_arc(
})); }));
} }
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -1596,10 +1565,10 @@ pub enum TangentialArcData {
} }
/// Draw a tangential arc. /// Draw a tangential arc.
pub async fn tangential_arc(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (TangentialArcData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (TangentialArcData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_tangential_arc(data, sketch, tag, args).await?; let new_sketch = inner_tangential_arc(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -1633,6 +1602,7 @@ async fn inner_tangential_arc(
data: TangentialArcData, data: TangentialArcData,
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from: Point2d = sketch.current_pen_position()?; let from: Point2d = sketch.current_pen_position()?;
@ -1644,7 +1614,7 @@ async fn inner_tangential_arc(
tangent_info.center_or_tangent_point tangent_info.center_or_tangent_point
}; };
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
let (center, to, ccw) = match data { let (center, to, ccw) = match data {
TangentialArcData::RadiusAndOffset { radius, offset } => { TangentialArcData::RadiusAndOffset { radius, offset } => {
@ -1723,18 +1693,18 @@ fn tan_arc_to(sketch: &Sketch, to: &[f64; 2]) -> ModelingCmd {
} }
/// Draw a tangential arc to a specific point. /// Draw a tangential arc to a specific point.
pub async fn tangential_arc_to(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn tangential_arc_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (to, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = super::args::FromArgs::from_args(&args, 0)?; let (to, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = super::args::FromArgs::from_args(&args, 0)?;
let new_sketch = inner_tangential_arc_to(to, sketch, tag, args).await?; let new_sketch = inner_tangential_arc_to(to, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
/// Draw a tangential arc to point some distance away.. /// Draw a tangential arc to point some distance away..
pub async fn tangential_arc_to_relative(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn tangential_arc_to_relative(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = super::args::FromArgs::from_args(&args, 0)?; let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = super::args::FromArgs::from_args(&args, 0)?;
let new_sketch = inner_tangential_arc_to_relative(delta, sketch, tag, args).await?; let new_sketch = inner_tangential_arc_to_relative(delta, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -1762,6 +1732,7 @@ async fn inner_tangential_arc_to(
to: [f64; 2], to: [f64; 2],
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from: Point2d = sketch.current_pen_position()?; let from: Point2d = sketch.current_pen_position()?;
@ -1780,7 +1751,7 @@ async fn inner_tangential_arc_to(
}); });
let delta = [to_x - from.x, to_y - from.y]; let delta = [to_x - from.x, to_y - from.y];
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?; args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?;
let current_path = Path::TangentialArcTo { let current_path = Path::TangentialArcTo {
@ -1831,6 +1802,7 @@ async fn inner_tangential_arc_to_relative(
delta: [f64; 2], delta: [f64; 2],
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from: Point2d = sketch.current_pen_position()?; let from: Point2d = sketch.current_pen_position()?;
@ -1864,7 +1836,7 @@ async fn inner_tangential_arc_to_relative(
})); }));
} }
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?; args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?;
let current_path = Path::TangentialArcTo { let current_path = Path::TangentialArcTo {
@ -1905,10 +1877,10 @@ pub struct BezierData {
} }
/// Draw a bezier curve. /// Draw a bezier curve.
pub async fn bezier_curve(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (BezierData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (BezierData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_bezier_curve(data, sketch, tag, args).await?; let new_sketch = inner_bezier_curve(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -1937,6 +1909,7 @@ async fn inner_bezier_curve(
data: BezierData, data: BezierData,
sketch: Sketch, sketch: Sketch,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
@ -1945,7 +1918,7 @@ async fn inner_bezier_curve(
let delta = data.to; let delta = data.to;
let to = [from.x + data.to[0], from.y + data.to[1]]; let to = [from.x + data.to[0], from.y + data.to[1]];
let id = uuid::Uuid::new_v4(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -1984,10 +1957,10 @@ async fn inner_bezier_curve(
} }
/// Use a sketch to cut a hole in another sketch. /// Use a sketch to cut a hole in another sketch.
pub async fn hole(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn hole(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (hole_sketch, sketch): (SketchSet, Sketch) = args.get_sketches()?; let (hole_sketch, sketch): (SketchSet, Sketch) = args.get_sketches()?;
let new_sketch = inner_hole(hole_sketch, sketch, args).await?; let new_sketch = inner_hole(hole_sketch, sketch, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
} }
@ -2025,11 +1998,16 @@ pub async fn hole(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
#[stdlib { #[stdlib {
name = "hole", name = "hole",
}] }]
async fn inner_hole(hole_sketch: SketchSet, sketch: Sketch, args: Args) -> Result<Sketch, KclError> { async fn inner_hole(
hole_sketch: SketchSet,
sketch: Sketch,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let hole_sketches: Vec<Sketch> = hole_sketch.into(); let hole_sketches: Vec<Sketch> = hole_sketch.into();
for hole_sketch in hole_sketches { for hole_sketch in hole_sketches {
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::Solid2dAddHole { ModelingCmd::from(mcmd::Solid2dAddHole {
object_id: sketch.id, object_id: sketch.id,
hole_id: hole_sketch.id, hole_id: hole_sketch.id,
@ -2040,7 +2018,7 @@ async fn inner_hole(hole_sketch: SketchSet, sketch: Sketch, args: Args) -> Resul
// suggestion (mike) // suggestion (mike)
// we also hide the source hole since its essentially "consumed" by this operation // we also hide the source hole since its essentially "consumed" by this operation
args.batch_modeling_cmd( args.batch_modeling_cmd(
uuid::Uuid::new_v4(), exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::ObjectVisible { ModelingCmd::from(mcmd::ObjectVisible {
object_id: hole_sketch.id, object_id: hole_sketch.id,
hidden: true, hidden: true,

View File

@ -1,7 +1,7 @@
//! Types used to send data to the test server. //! Types used to send data to the test server.
use crate::{ use crate::{
executor::{ExecutorContext, ExecutorSettings}, executor::{ExecutorContext, ExecutorSettings, IdGenerator},
settings::types::UnitLength, settings::types::UnitLength,
}; };
@ -29,7 +29,9 @@ async fn do_execute_and_snapshot(ctx: &ExecutorContext, code: &str) -> anyhow::R
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast()?; let program = parser.ast()?;
let snapshot = ctx.execute_and_prepare_snapshot(&program).await?; let snapshot = ctx
.execute_and_prepare_snapshot(&program, IdGenerator::default())
.await?;
// Create a temporary file to write the output to. // Create a temporary file to write the output to.
let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4())); let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));

View File

@ -1,142 +0,0 @@
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 16 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 824 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 1 test
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
test visuals::server_rack_heavy has been running for over 60 seconds
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
extrude
test visuals::server_rack_heavy ... FAILED
failures:
failures:
visuals::server_rack_heavy
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 142 filtered out; finished in 279.58s

View File

@ -16,6 +16,7 @@ use wasm_bindgen::prelude::*;
pub async fn execute_wasm( pub async fn execute_wasm(
program_str: &str, program_str: &str,
memory_str: &str, memory_str: &str,
id_generator_str: &str,
units: &str, units: &str,
engine_manager: kcl_lib::engine::conn_wasm::EngineCommandManager, engine_manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
fs_manager: kcl_lib::fs::wasm::FileSystemManager, fs_manager: kcl_lib::fs::wasm::FileSystemManager,
@ -26,6 +27,8 @@ pub async fn execute_wasm(
let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?; let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
let memory: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?; let memory: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
let id_generator: kcl_lib::executor::IdGenerator =
serde_json::from_str(id_generator_str).map_err(|e| e.to_string())?;
let units = kcl_lib::settings::types::UnitLength::from_str(units).map_err(|e| e.to_string())?; let units = kcl_lib::settings::types::UnitLength::from_str(units).map_err(|e| e.to_string())?;
let engine: std::sync::Arc<Box<dyn kcl_lib::engine::EngineManager>> = if is_mock { let engine: std::sync::Arc<Box<dyn kcl_lib::engine::EngineManager>> = if is_mock {
@ -58,13 +61,16 @@ pub async fn execute_wasm(
context_type, context_type,
}; };
let exec_state = ctx.run(&program, Some(memory)).await.map_err(String::from)?; let exec_state = ctx
.run(&program, Some(memory), id_generator)
.await
.map_err(String::from)?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
// gloo-serialize crate instead. // gloo-serialize crate instead.
// DO NOT USE serde_wasm_bindgen::to_value(&memory).map_err(|e| e.to_string()) // DO NOT USE serde_wasm_bindgen::to_value(&exec_state).map_err(|e| e.to_string())
// it will break the frontend. // it will break the frontend.
JsValue::from_serde(&exec_state.memory).map_err(|e| e.to_string()) JsValue::from_serde(&exec_state).map_err(|e| e.to_string())
} }
// wasm_bindgen wrapper for execute // wasm_bindgen wrapper for execute
@ -93,7 +99,7 @@ pub async fn make_default_planes(
.await .await
.map_err(|e| format!("{:?}", e))?; .map_err(|e| format!("{:?}", e))?;
let default_planes = engine let default_planes = engine
.new_default_planes(Default::default()) .new_default_planes(&mut kcl_lib::executor::IdGenerator::default(), Default::default())
.await .await
.map_err(String::from)?; .map_err(String::from)?;

View File

@ -1,4 +1,8 @@
use kcl_lib::{ast::types::Program, errors::KclError, executor::ExecutorContext}; use kcl_lib::{
ast::types::Program,
errors::KclError,
executor::{ExecutorContext, IdGenerator},
};
macro_rules! gen_test { macro_rules! gen_test {
($file:ident) => { ($file:ident) => {
@ -22,12 +26,12 @@ macro_rules! gen_test_fail {
} }
async fn run(code: &str) { async fn run(code: &str) {
let (ctx, program) = setup(code).await; let (ctx, program, id_generator) = setup(code).await;
ctx.run(&program, None).await.unwrap(); ctx.run(&program, None, id_generator).await.unwrap();
} }
async fn setup(program: &str) -> (ExecutorContext, Program) { async fn setup(program: &str) -> (ExecutorContext, Program, IdGenerator) {
let tokens = kcl_lib::token::lexer(program).unwrap(); let tokens = kcl_lib::token::lexer(program).unwrap();
let parser = kcl_lib::parser::Parser::new(tokens); let parser = kcl_lib::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
@ -40,12 +44,12 @@ async fn setup(program: &str) -> (ExecutorContext, Program) {
settings: Default::default(), settings: Default::default(),
context_type: kcl_lib::executor::ContextType::Mock, context_type: kcl_lib::executor::ContextType::Mock,
}; };
(ctx, program) (ctx, program, IdGenerator::default())
} }
async fn run_fail(code: &str) -> KclError { async fn run_fail(code: &str) -> KclError {
let (ctx, program) = setup(code).await; let (ctx, program, id_generator) = setup(code).await;
let Err(e) = ctx.run(&program, None).await else { let Err(e) = ctx.run(&program, None, id_generator).await else {
panic!("Expected this KCL program to fail, but it (incorrectly) never threw an error."); panic!("Expected this KCL program to fail, but it (incorrectly) never threw an error.");
}; };
e e

View File

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use kcl_lib::{ use kcl_lib::{
ast::{modify::modify_ast_for_sketch, types::Program}, ast::{modify::modify_ast_for_sketch, types::Program},
executor::{ExecutorContext, KclValue, PlaneType, Sketch, SourceRange}, executor::{ExecutorContext, IdGenerator, KclValue, PlaneType, Sketch, SourceRange},
}; };
use kittycad_modeling_cmds::{each_cmd as mcmd, length_unit::LengthUnit, shared::Point3d, ModelingCmd}; use kittycad_modeling_cmds::{each_cmd as mcmd, length_unit::LengthUnit, shared::Point3d, ModelingCmd};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@ -35,7 +35,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
let parser = kcl_lib::parser::Parser::new(tokens); let parser = kcl_lib::parser::Parser::new(tokens);
let program = parser.ast()?; let program = parser.ast()?;
let ctx = kcl_lib::executor::ExecutorContext::new(&client, Default::default()).await?; let ctx = kcl_lib::executor::ExecutorContext::new(&client, Default::default()).await?;
let exec_state = ctx.run(&program, None).await?; let exec_state = ctx.run(&program, None, IdGenerator::default()).await?;
// We need to get the sketch ID. // We need to get the sketch ID.
// Get the sketch ID from memory. // Get the sketch ID from memory.