Compare commits

..

6 Commits

116 changed files with 2521 additions and 14554 deletions

View File

@ -25,7 +25,6 @@ jobs:
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
outputs:
version: ${{ steps.export_version.outputs.version }}
notes: ${{ steps.export_version.outputs.notes }}
steps:
- uses: actions/checkout@v4
@ -52,35 +51,35 @@ jobs:
run: |
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
- name: Generate release notes
env:
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
run: |
echo "$NOTES" > release-notes.md
cat release-notes.md
- uses: actions/upload-artifact@v3
with:
name: prepared-files
path: |
package.json
src/wasm-lib/pkg/wasm_lib*
release-notes.md
- id: export_version
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
- id: export_notes
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
- name: Prepare electron-builder.yml file for nightly
if: ${{ github.event_name == 'schedule' }}
run: |
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/nightly"' electron-builder.yml
- uses: actions/upload-artifact@v3
if: ${{ github.event_name == 'schedule' }}
with:
name: prepared-files-nightly
path: |
electron-builder.yml
- name: Prepare electron-builder.yml file for updater test
if: ${{ env.CUT_RELEASE_PR == 'true' }}
run: |
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test-release-notes"' electron-builder.yml
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test"' electron-builder.yml
- uses: actions/upload-artifact@v3
if: ${{ env.CUT_RELEASE_PR == 'true' }}
with:
name: prepared-files-updater-test
path: |
@ -119,7 +118,16 @@ jobs:
cp prepared-files/src/wasm-lib/pkg/wasm_lib_bg.wasm public
mkdir src/wasm-lib/pkg
cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
cp prepared-files/release-notes.md release-notes.md
- uses: actions/download-artifact@v3
if: ${{ github.event_name == 'schedule' }}
name: prepared-files-nightly
- name: Copy updated electron-builder.yml file for nightly build
if: ${{ github.event_name == 'schedule' }}
run: |
ls -R prepared-files-nightly
cp prepared-files-nightly/electron-builder.yml electron-builder.yml
- name: Sync node version and setup cache
uses: actions/setup-node@v4
@ -165,11 +173,17 @@ jobs:
- uses: actions/upload-artifact@v3
with:
name: out-${{ matrix.os }}
name: out-arm64-${{ matrix.os }}
path: |
out/Zoo*.*
out/Zoo*arm64*.*
out/latest*.yml
- uses: actions/upload-artifact@v3
with:
name: out-x64-${{ matrix.os }}
path: |
out/Zoo*x*64*.*
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
- uses: actions/download-artifact@v3
@ -189,10 +203,16 @@ jobs:
- uses: actions/upload-artifact@v3
if: ${{ env.CUT_RELEASE_PR == 'true' }}
with:
name: updater-test-${{ matrix.os }}
name: updater-test-arm64-${{ matrix.os }}
path: |
out/Zoo*.*
out/latest*.yml
out/Zoo*arm64*.*
- uses: actions/upload-artifact@v3
if: ${{ env.CUT_RELEASE_PR == 'true' }}
with:
name: updater-test-x64-${{ matrix.os }}
path: |
out/Zoo*x64*.*
publish-apps-release:
@ -205,7 +225,7 @@ jobs:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
NOTES: ${{ needs.prepare-files.outputs.notes }}
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
@ -214,17 +234,32 @@ jobs:
- uses: actions/download-artifact@v3
with:
name: out-windows-2022
name: out-arm64-windows-2022
path: out
- uses: actions/download-artifact@v3
with:
name: out-macos-14
name: out-x64-windows-2022
path: out
- uses: actions/download-artifact@v3
with:
name: out-ubuntu-22.04
name: out-arm64-macos-14
path: out
- uses: actions/download-artifact@v3
with:
name: out-x64-macos-14
path: out
- uses: actions/download-artifact@v3
with:
name: out-arm64-ubuntu-22.04
path: out
- uses: actions/download-artifact@v3
with:
name: out-x64-ubuntu-22.04
path: out
- name: Generate the download static endpoint

View File

@ -2,7 +2,7 @@
## Zoo Modeling App
download at [zoo.dev/modeling-app/download](https://zoo.dev/modeling-app/download)
live at [app.zoo.dev](https://app.zoo.dev/)
A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
@ -57,7 +57,7 @@ yarn install
followed by:
```
yarn build:wasm
yarn build:wasm-dev
```
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
```
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
```
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.
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.
### Development environment variables
@ -91,13 +91,13 @@ Third-Party Cookies".
## Desktop
To spin up the desktop app, `yarn install` and `yarn build:wasm` need to have been done before hand then
To spin up the desktop app, `yarn install` and `yarn build:wasm-dev` need to have been done before hand then
```
yarn tron:start
yarn electron:start
```
This will start the application and hot-reload on changes.
This will start the application and hot-reload on changed.
Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
@ -128,18 +128,7 @@ Before you submit a contribution PR to this repo, please ensure that:
## Release a new version
#### 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>
```
#### 1. Bump the versions by running `./make-release.sh` and create a Cut Release PR
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;
@ -148,32 +137,28 @@ 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.
#### 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.
**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.
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.
#### 3. Manually test artifacts from the Cut Release PR
#### 2. Smoke 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.
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.
We don't have a strict process, but click around and check for anything obvious, 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).
#### 4. Merge the Cut Release PR
#### 3. 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.
#### 5. Publish the release
#### 4. 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_.
#### 6. Profit
#### 5. Profit
A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter.
@ -334,16 +319,7 @@ Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testin
```bash
cd src/wasm-lib
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
cargo test
```
### Mapping CI CD jobs to local commands

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
---
title: "KclValue"
excerpt: "Any KCL value."
excerpt: "A memory item."
layout: manual
---
Any KCL value.
A memory item.
@ -80,7 +80,7 @@ A plane.
|----------|------|-------------|----------|
| `type` |enum: `Plane`| | No |
| `id` |`string`| The id of the plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A memory item. | 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 |
| `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 |
|----------|------|-------------|----------|
| `type` |enum: `Function`| | No |
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| A memory item. | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| A memory item. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -1,80 +0,0 @@
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

@ -13,13 +13,6 @@ type mouseParams = {
pixelDiff: number
}
type SceneSerialised = {
camera: {
position: [number, number, number]
target: [number, number, number]
}
}
export class SceneFixture {
public page: Page
@ -29,22 +22,6 @@ export class SceneFixture {
this.page = page
this.reConstruct(page)
}
private _serialiseScene = async (): Promise<SceneSerialised> => {
const camera = await this.getCameraInfo()
return {
camera,
}
}
expectState = async (expected: SceneSerialised) => {
return expect
.poll(() => this._serialiseScene(), {
message: `Expected scene state to match`,
})
.toEqual(expected)
}
reConstruct = (page: Page) => {
this.page = page
@ -54,7 +31,7 @@ export class SceneFixture {
makeMouseHelpers = (
x: number,
y: number,
{ steps }: { steps: number } = { steps: 20 }
{ steps }: { steps: number } = { steps: 5000 }
) =>
[
(clickParams?: mouseParams) => {
@ -110,36 +87,6 @@ export class SceneFixture {
)
await closeDebugPanel(this.page)
}
/** Forces a refresh of the camera position and target displayed
* in the debug panel and then returns the values of the fields
*/
async getCameraInfo() {
await openAndClearDebugPanel(this.page)
await sendCustomCmd(this.page, {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await this.waitForExecutionDone()
const position = await Promise.all([
this.page.getByTestId('cam-x-position').inputValue().then(Number),
this.page.getByTestId('cam-y-position').inputValue().then(Number),
this.page.getByTestId('cam-z-position').inputValue().then(Number),
])
const target = await Promise.all([
this.page.getByTestId('cam-x-target').inputValue().then(Number),
this.page.getByTestId('cam-y-target').inputValue().then(Number),
this.page.getByTestId('cam-z-target').inputValue().then(Number),
])
await closeDebugPanel(this.page)
return {
position,
target,
}
}
waitForExecutionDone = async () => {
await expect(this.exeIndicator).toBeVisible()
}
@ -167,17 +114,4 @@ export class SceneFixture {
)
})
}
get gizmo() {
return this.page.locator('[aria-label*=gizmo]')
}
async clickGizmoMenuItem(name: string) {
await this.gizmo.click({ button: 'right' })
const buttonToTest = this.page.getByRole('button', {
name: name,
})
await expect(buttonToTest).toBeVisible()
await buttonToTest.click()
}
}

View File

@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test'
import { test, expect, Page } from '@playwright/test'
import {
doExport,
executorInputPath,
@ -618,30 +618,31 @@ test(
'Deleting projects, can delete individual project, can still create projects after deleting all',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const projectData = [
['router-template-slate', 'cylinder.kcl'],
['bracket', 'focusrite_scarlett_mounting_braket.kcl'],
['lego', 'lego.kcl'],
]
const { electronApp, page } = await setupElectron({
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 })
page.on('console', console.log)
const createProjectAndRenameItTest = async ({
name,
page,
}: {
name: string
page: Page
}) => {
await test.step(`Create and rename project ${name}`, async () => {
await createProjectAndRenameIt({ name, page })
})
}
// 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 createProjectAndRenameItTest({ name: 'router-template-slate', page })
await createProjectAndRenameItTest({ name: 'bracket', page })
await createProjectAndRenameItTest({ name: 'lego', page })
await test.step('delete the middle project, i.e. the bracket project', async () => {
const project = page.getByText('bracket')
@ -743,26 +744,8 @@ test(
'Can sort projects on home page',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const projectData = [
['router-template-slate', 'cylinder.kcl'],
['bracket', 'focusrite_scarlett_mounting_braket.kcl'],
['lego', 'lego.kcl'],
]
const { electronApp, page } = await setupElectron({
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 })
@ -770,6 +753,24 @@ test(
page.on('console', console.log)
const createProjectAndRenameItTest = async ({
name,
page,
}: {
name: string
page: Page
}) => {
await test.step(`Create and rename project ${name}`, async () => {
await createProjectAndRenameIt({ name, page })
})
}
// 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 createProjectAndRenameItTest({ name: 'router-template-slate', page })
await createProjectAndRenameItTest({ name: 'bracket', page })
await createProjectAndRenameItTest({ name: 'lego', page })
await test.step('should be shorted by modified initially', async () => {
const lastModifiedButton = page.getByRole('button', {
name: 'Last Modified',

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 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

@ -1,18 +1,18 @@
import { _test, _expect } from './playwright-deprecated'
import { test } from './fixtures/fixtureSetup'
import { test, expect } from '@playwright/test'
import { getUtils, setup, tearDown } from './test-utils'
import { uuidv4 } from 'lib/utils'
import { TEST_CODE_GIZMO } from './storageStates'
_test.beforeEach(async ({ context, page }, testInfo) => {
test.beforeEach(async ({ context, page }, testInfo) => {
await setup(context, page, testInfo)
})
_test.afterEach(async ({ page }, testInfo) => {
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
_test.describe('Testing Gizmo', () => {
test.describe('Testing Gizmo', () => {
const cases = [
{
testDescription: 'top view',
@ -57,7 +57,7 @@ _test.describe('Testing Gizmo', () => {
expectedCameraTarget,
testDescription,
} of cases) {
_test(`check ${testDescription}`, async ({ page, browserName }) => {
test(`check ${testDescription}`, async ({ page, browserName }) => {
const u = await getUtils(page)
await page.addInitScript((TEST_CODE_GIZMO) => {
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
@ -117,30 +117,30 @@ _test.describe('Testing Gizmo', () => {
await Promise.all([
// position
_expect(page.getByTestId('cam-x-position')).toHaveValue(
expect(page.getByTestId('cam-x-position')).toHaveValue(
expectedCameraPosition.x.toString()
),
_expect(page.getByTestId('cam-y-position')).toHaveValue(
expect(page.getByTestId('cam-y-position')).toHaveValue(
expectedCameraPosition.y.toString()
),
_expect(page.getByTestId('cam-z-position')).toHaveValue(
expect(page.getByTestId('cam-z-position')).toHaveValue(
expectedCameraPosition.z.toString()
),
// target
_expect(page.getByTestId('cam-x-target')).toHaveValue(
expect(page.getByTestId('cam-x-target')).toHaveValue(
expectedCameraTarget.x.toString()
),
_expect(page.getByTestId('cam-y-target')).toHaveValue(
expect(page.getByTestId('cam-y-target')).toHaveValue(
expectedCameraTarget.y.toString()
),
_expect(page.getByTestId('cam-z-target')).toHaveValue(
expect(page.getByTestId('cam-z-target')).toHaveValue(
expectedCameraTarget.z.toString()
),
])
})
}
_test('Context menu and popover menu', async ({ page }) => {
test('Context menu and popover menu', async ({ page }) => {
const testCase = {
testDescription: 'Right view',
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
@ -196,7 +196,7 @@ _test.describe('Testing Gizmo', () => {
const buttonToTest = page.getByRole('button', {
name: testCase.testDescription,
})
await _expect(buttonToTest).toBeVisible()
await expect(buttonToTest).toBeVisible()
await buttonToTest.click()
// Now assert we've moved to the correct view
@ -215,23 +215,23 @@ _test.describe('Testing Gizmo', () => {
await Promise.all([
// position
_expect(page.getByTestId('cam-x-position')).toHaveValue(
expect(page.getByTestId('cam-x-position')).toHaveValue(
testCase.expectedCameraPosition.x.toString()
),
_expect(page.getByTestId('cam-y-position')).toHaveValue(
expect(page.getByTestId('cam-y-position')).toHaveValue(
testCase.expectedCameraPosition.y.toString()
),
_expect(page.getByTestId('cam-z-position')).toHaveValue(
expect(page.getByTestId('cam-z-position')).toHaveValue(
testCase.expectedCameraPosition.z.toString()
),
// target
_expect(page.getByTestId('cam-x-target')).toHaveValue(
expect(page.getByTestId('cam-x-target')).toHaveValue(
testCase.expectedCameraTarget.x.toString()
),
_expect(page.getByTestId('cam-y-target')).toHaveValue(
expect(page.getByTestId('cam-y-target')).toHaveValue(
testCase.expectedCameraTarget.y.toString()
),
_expect(page.getByTestId('cam-z-target')).toHaveValue(
expect(page.getByTestId('cam-z-target')).toHaveValue(
testCase.expectedCameraTarget.z.toString()
),
])
@ -242,60 +242,8 @@ _test.describe('Testing Gizmo', () => {
const gizmoPopoverButton = page.getByRole('button', {
name: 'view settings',
})
await _expect(gizmoPopoverButton).toBeVisible()
await expect(gizmoPopoverButton).toBeVisible()
await gizmoPopoverButton.click()
await _expect(buttonToTest).toBeVisible()
})
})
test.describe(`Testing gizmo, fixture-based`, () => {
test('Center on selection from menu', async ({
app,
cmdBar,
editor,
toolbar,
scene,
}) => {
test.skip(
process.platform === 'win32',
'Fails on windows in CI, can not be replicated locally on windows.'
)
await test.step(`Setup`, async () => {
const file = await app.getInputFile('test-circle-extrude.kcl')
await app.initialise(file)
await scene.expectState({
camera: {
position: [4982.21, -23865.37, 13810.64],
target: [4982.21, 0, 2737.1],
},
})
})
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
await test.step(`Select an edge of this circle`, async () => {
const circleSnippet =
'circle({ center: [318.33, 168.1], radius: 182.8 }, %)'
await moveToCircle()
await clickCircle()
await editor.expectState({
activeLines: [circleSnippet.slice(-5)],
highlightedCode: circleSnippet,
diagnostics: [],
})
})
await test.step(`Center on selection from menu`, async () => {
await scene.clickGizmoMenuItem('Center view on selection')
})
await test.step(`Verify the camera moved`, async () => {
await scene.expectState({
camera: {
position: [0, -23865.37, 11073.54],
target: [0, 0, 0],
},
})
})
await expect(buttonToTest).toBeVisible()
})
})

View File

@ -1208,12 +1208,6 @@ extrude001 = extrude(50, sketch001)
test('Deselecting line tool should mean nothing happens on click', async ({
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)
await page.setViewportSize({ width: 1200, height: 500 })
@ -1234,7 +1228,6 @@ extrude001 = extrude(50, sketch001)
200
)
// Clicks the XZ Plane in the page
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
@ -1243,11 +1236,6 @@ extrude001 = extrude(50, sketch001)
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()
// deselect the line tool by clicking it

View File

@ -9,7 +9,6 @@ import {
executorInputPath,
} from './test-utils'
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
import { SETTINGS_FILE_NAME } from 'lib/constants'
import {
TEST_SETTINGS_KEY,
TEST_SETTINGS_CORRUPTED,
@ -344,7 +343,7 @@ test.describe('Testing settings', () => {
// Selectors and constants
const errorHeading = page.getByRole('heading', {
name: 'An unexpected error occurred',
name: 'An unextected error occurred',
})
const projectDirLink = page.getByText('Loaded from')
@ -373,7 +372,7 @@ test.describe('Testing settings', () => {
// Selectors and constants
const errorHeading = page.getByRole('heading', {
name: 'An unexpected error occurred',
name: 'An unextected error occurred',
})
const projectDirLink = page.getByText('Loaded from')
@ -385,66 +384,6 @@ test.describe('Testing settings', () => {
}
)
// It was much easier to test the logo color than the background stream color.
test(
'user settings reload on external change, on project and modeling view',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const {
electronApp,
page,
dir: projectDirName,
} = await setupElectron({
testInfo,
appSettings: {
app: {
// Doesn't matter what you set it to. It will
// default to 264.5
themeColor: '0',
},
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const logoLink = page.getByTestId('app-logo')
const projectDirLink = page.getByText('Loaded from')
await test.step('Wait for project view', async () => {
await expect(projectDirLink).toBeVisible()
await expect(logoLink).toHaveCSS('--primary-hue', '264.5')
})
const changeColor = async (color: string) => {
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
let tomlStr = await fsp.readFile(tempSettingsFilePath, 'utf-8')
tomlStr = tomlStr.replace(/(themeColor = ")[0-9]+(")/, `$1${color}$2`)
await fsp.writeFile(tempSettingsFilePath, tomlStr)
}
await test.step('Check color of logo changed', async () => {
await changeColor('99')
await expect(logoLink).toHaveCSS('--primary-hue', '99')
})
await test.step('Check color of logo changed when in modeling view', async () => {
await page.getByRole('button', { name: 'New project' }).click()
await page.getByTestId('project-link').first().click()
await page.getByRole('button', { name: 'Dismiss' }).click()
await changeColor('58')
await expect(logoLink).toHaveCSS('--primary-hue', '58')
})
await test.step('Check going back to projects view still changes the color', async () => {
await logoLink.click()
await expect(projectDirLink).toBeVisible()
await changeColor('21')
await expect(logoLink).toHaveCSS('--primary-hue', '21')
})
await electronApp.close()
}
)
test(
`Closing settings modal should go back to the original file being viewed`,
{ tag: '@electron' },

View File

@ -32,10 +32,10 @@ win:
arch:
- x64
- arm64
# - target: msi
# arch:
# - x64
# - arm64
- target: msi
arch:
- x64
- arm64
signingHashAlgorithms:
- sha256
sign: "./sign-win.js"
@ -47,9 +47,9 @@ win:
mimeType: text/vnd.zoo.kcl
description: Zoo KCL File
role: Editor
# msi:
# oneClick: false
# perMachine: true
msi:
oneClick: false
perMachine: true
nsis:
oneClick: false
perMachine: true
@ -73,5 +73,3 @@ publish:
- provider: generic
url: https://dl.zoo.dev/releases/modeling-app
channel: latest
releaseInfo:
releaseNotesFile: release-notes.md

5
interface.d.ts vendored
View File

@ -23,6 +23,7 @@ export interface IElectronAPI {
callback: (eventType: string, path: string) => void
) => void
watchFileOff: (path: string) => void
watchFileObliterate: () => void
readFile: (path: string) => ReturnType<fs.readFile>
writeFile: (
path: string,
@ -69,13 +70,9 @@ export interface IElectronAPI {
kittycad: (access: string, args: any) => any
listMachines: () => Promise<MachinesListing>
getMachineApiIp: () => Promise<string | null>
onUpdateDownloadStart: (
callback: (value: { version: string }) => void
) => Electron.IpcRenderer
onUpdateDownloaded: (
callback: (value: string) => void
) => Electron.IpcRenderer
onUpdateError: (callback: (value: { error: Error }) => void) => Electron
appRestart: () => void
}

View File

@ -1,6 +1,6 @@
{
"name": "zoo-modeling-app",
"version": "0.25.6",
"version": "0.25.5",
"private": true,
"productName": "Zoo Modeling App",
"author": {
@ -26,7 +26,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "2.0.7",
"@kittycad/lib": "^2.0.1",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.1",
"@react-hook/resize-observer": "^2.0.1",
@ -36,7 +36,6 @@
"@xstate/inspect": "^0.8.0",
"@xstate/react": "^4.1.1",
"bonjour-service": "^1.2.1",
"chokidar": "^4.0.1",
"codemirror": "^6.0.1",
"decamelize": "^6.0.0",
"electron-squirrel-startup": "^1.0.1",

View File

@ -893,7 +893,6 @@ export class CameraControls {
type: 'zoom_to_fit',
object_ids: [], // leave empty to zoom to all objects
padding: 0.2, // padding around the objects
animated: false, // don't animate the zoom for now
},
})
}

View File

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

View File

@ -391,14 +391,12 @@ export class SceneEntities {
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
prepared
const { execState } = await executeAst({
const { programMemory } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
const sketch = sketchFromPathToNode({
pathToNode: sketchPathToNode,
ast: maybeModdedAst,
@ -803,14 +801,12 @@ export class SceneEntities {
updateRectangleSketch(sketchInit, x, y, tags[0])
}
const { execState } = await executeAst({
const { programMemory } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName),
@ -852,14 +848,12 @@ export class SceneEntities {
await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'Finish rectangle' })
const { execState } = await executeAst({
const { programMemory } = await executeAst({
ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
// Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
@ -971,14 +965,12 @@ export class SceneEntities {
modded = moddedResult.modifiedAst
}
const { execState } = await executeAst({
const { programMemory } = await executeAst({
ast: modded,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName),
@ -1325,14 +1317,12 @@ export class SceneEntities {
// 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
codeManager.updateCodeEditor(code)
const { execState } = await executeAst({
const { programMemory } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const maybeSketch = programMemory.get(variableDeclarationName)

View File

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

View File

@ -91,7 +91,7 @@ function CommandBarSelectionInput({
<form id="arg-form" onSubmit={handleSubmit}>
<label
className={
'relative flex flex-col mx-4 my-4 ' +
'relative flex items-center mx-4 my-4 ' +
(!hasSubmitted || canSubmitSelection || 'text-destroy-50')
}
>
@ -100,18 +100,13 @@ function CommandBarSelectionInput({
: `Please select ${
arg.multiple ? 'one or more ' : 'one '
}${getSemanticSelectionType(arg.selectionTypes).join(' or ')}`}
{arg.warningMessage && (
<p className="text-warn-80 bg-warn-10 px-2 py-1 rounded-sm mt-3 mr-2 -mb-2 w-full text-sm cursor-default">
{arg.warningMessage}
</p>
)}
<input
id="selection"
name="selection"
ref={inputRef}
required
placeholder="Select an entity with your mouse"
className="absolute inset-0 w-full h-full opacity-0 cursor-default"
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
onKeyDown={(event) => {
if (event.key === 'Backspace') {
stepBack()

View File

@ -1,111 +0,0 @@
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

@ -1,45 +0,0 @@
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

@ -28,7 +28,6 @@ import {
import { Popover } from '@headlessui/react'
import { CustomIcon } from './CustomIcon'
import { reportRejection } from 'lib/trap'
import { useModelingContext } from 'hooks/useModelingContext'
const CANVAS_SIZE = 80
const FRUSTUM_SIZE = 0.5
@ -63,7 +62,6 @@ export default function Gizmo() {
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
const cameraPassiveUpdateTimer = useRef(0)
const raycasterPassiveUpdateTimer = useRef(0)
const { send: modelingSend } = useModelingContext()
const menuItems = useMemo(
() => [
...Object.entries(axisNamesSemantic).map(([axisName, axisSemantic]) => (
@ -78,7 +76,6 @@ export default function Gizmo() {
{axisSemantic} view
</ContextMenuItem>
)),
<ContextMenuDivider />,
<ContextMenuItem
onClick={() => {
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
@ -86,13 +83,6 @@ export default function Gizmo() {
>
Reset view
</ContextMenuItem>,
<ContextMenuItem
onClick={() => {
modelingSend({ type: 'Center camera on selection' })
}}
>
Center view on selection
</ContextMenuItem>,
<ContextMenuDivider />,
<ContextMenuItemRefresh />,
],

View File

@ -83,7 +83,6 @@ import {
} from 'lang/std/engineConnection'
import { submitAndAwaitTextToKcl } from 'lib/textToCad'
import { useFileContext } from 'hooks/useFileContext'
import { uuidv4 } from 'lib/utils'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -149,13 +148,6 @@ export const ModelingMachineProvider = ({
},
'sketch exit execute': ({ context: { store } }) => {
;(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'
if (cameraProjection.current === 'perspective') {
@ -251,17 +243,6 @@ export const ModelingMachineProvider = ({
return {}
},
}),
'Center camera on selection': () => {
engineCommandManager
.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_center_to_selection',
},
})
.catch(reportRejection)
},
'Set sketchDetails': assign(({ context: { sketchDetails }, event }) => {
if (event.type !== 'Delete segment') return {}
if (!sketchDetails) return {}
@ -1056,11 +1037,6 @@ export const ModelingMachineProvider = ({
modelingSend({ type: 'Delete selection' })
})
// Allow ctrl+alt+c to center to selection
useHotkeys(['mod + alt + c'], () => {
modelingSend({ type: 'Center camera on selection' })
})
useStateMachineCommands({
machineId: 'modeling',
state: modelingState,

View File

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

View File

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

View File

@ -1,10 +1,9 @@
import { trap } from 'lib/trap'
import { useMachine } from '@xstate/react'
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
import { PATHS } from 'lib/paths'
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
import withBaseUrl from '../lib/withBaseURL'
import React, { createContext, useEffect, useState } from 'react'
import React, { createContext, useEffect } from 'react'
import useStateMachineCommands from '../hooks/useStateMachineCommands'
import { settingsMachine } from 'machines/settingsMachine'
import { toast } from 'react-hot-toast'
@ -16,6 +15,7 @@ import {
} from 'lib/theme'
import decamelize from 'decamelize'
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
import { isDesktop } from 'lib/isDesktop'
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
import {
kclManager,
@ -33,14 +33,8 @@ import {
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Command } from 'lib/commandTypes'
import { BaseUnit } from 'lib/settings/settingsTypes'
import {
saveSettings,
loadAndValidateSettings,
} from 'lib/settings/settingsUtils'
import { saveSettings } from 'lib/settings/settingsUtils'
import { reportRejection } from 'lib/trap'
import { getAppSettingsFilePath } from 'lib/desktop'
import { isDesktop } from 'lib/isDesktop'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -105,9 +99,6 @@ export const SettingsAuthProviderBase = ({
const location = useLocation()
const navigate = useNavigate()
const { commandBarSend } = useCommandsContext()
const [settingsPath, setSettingsPath] = useState<string | undefined>(
undefined
)
const [settingsState, settingsSend, settingsActor] = useMachine(
settingsMachine.provide({
@ -200,11 +191,7 @@ export const SettingsAuthProviderBase = ({
console.error('Error executing AST after settings change', e)
}
},
persistSettings: ({ context, event }) => {
// Without this, when a user changes the file, it'd
// create a detection loop with the file-system watcher.
if (event.doNotPersist) return
persistSettings: ({ context }) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
saveSettings(context, loadedProject?.project?.path)
},
@ -214,23 +201,6 @@ export const SettingsAuthProviderBase = ({
)
settingsStateRef = settingsState.context
useEffect(() => {
if (!isDesktop()) return
getAppSettingsFilePath().then(setSettingsPath).catch(trap)
}, [])
useFileSystemWatcher(
async () => {
const data = await loadAndValidateSettings(loadedProject?.project?.path)
settingsSend({
type: 'Set all settings',
settings: data.settings,
doNotPersist: true,
})
},
settingsPath ? [settingsPath] : []
)
// Add settings commands to the command bar
// They're treated slightly differently than other commands
// Because their state machine doesn't have a meaningful .nextEvents,

View File

@ -1,5 +1,4 @@
import { isDesktop } from 'lib/isDesktop'
import { reportRejection } from 'lib/trap'
import { useEffect, useState, useRef } from 'react'
type Path = string
@ -12,13 +11,13 @@ type Path = string
// watcher.addListener(() => { ... }).
export const useFileSystemWatcher = (
callback: (path: Path) => Promise<void>,
callback: (path: Path) => void,
dependencyArray: Path[]
): void => {
// Track a ref to the callback. This is how we get the callback updated
// across the NodeJS<->Browser boundary.
const callbackRef = useRef<{ fn: (path: Path) => Promise<void> }>({
fn: async (_path) => {},
const callbackRef = useRef<{ fn: (path: Path) => void }>({
fn: (_path) => {},
})
useEffect(() => {
@ -36,9 +35,7 @@ export const useFileSystemWatcher = (
if (!isDesktop()) return
return () => {
for (let path of dependencyArray) {
window.electron.watchFileOff(path)
}
window.electron.watchFileObliterate()
}
}, [])
@ -49,9 +46,6 @@ export const useFileSystemWatcher = (
]
}
const hasDiff =
difference(dependencyArray, dependencyArrayTracked)[0].length !== 0
// Removing 1 watcher at a time is only possible because in a filesystem,
// a path is unique (there can never be two paths with the same name).
// Otherwise we would have to obliterate() the whole list and reconstruct it.
@ -59,8 +53,6 @@ export const useFileSystemWatcher = (
// The hook is useless on web.
if (!isDesktop()) return
if (!hasDiff) return
const [pathsRemoved, pathsRemaining] = difference(
dependencyArrayTracked,
dependencyArray
@ -70,10 +62,10 @@ export const useFileSystemWatcher = (
}
const [pathsAdded] = difference(dependencyArray, dependencyArrayTracked)
for (let path of pathsAdded) {
window.electron.watchFileOn(path, (_eventType: string, path: Path) => {
callbackRef.current.fn(path).catch(reportRejection)
})
window.electron.watchFileOn(path, (_eventType: string, path: Path) =>
callbackRef.current.fn(path)
)
}
setDependencyArrayTracked(pathsRemaining.concat(pathsAdded))
}, [hasDiff])
}, [difference(dependencyArray, dependencyArrayTracked)[0].length !== 0])
}

View File

@ -8,7 +8,6 @@ import ModalContainer from 'react-modal-promise'
import { isDesktop } from 'lib/isDesktop'
import { AppStreamProvider } from 'AppState'
import { ToastUpdate } from 'components/ToastUpdate'
import { AUTO_UPDATER_TOAST_ID } from 'lib/constants'
// uncomment for xstate inspector
// import { DEV } from 'env'
@ -54,22 +53,7 @@ root.render(
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
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,
})
})
isDesktop() &&
window.electron.onUpdateDownloaded((version: string) => {
const message = `A new update (${version}) was downloaded and will be available next time you open the app.`
console.log(message)
@ -80,7 +64,6 @@ if (isDesktop()) {
window.electron.appRestart()
},
}),
{ duration: 30000, id: AUTO_UPDATER_TOAST_ID }
{ duration: 30000 }
)
})
}

View File

@ -8,8 +8,6 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import {
CallExpression,
emptyExecState,
ExecState,
initPromise,
parse,
PathToNode,
@ -44,7 +42,6 @@ export class KclManager {
},
digest: null,
}
private _execState: ExecState = emptyExecState()
private _programMemory: ProgramMemory = ProgramMemory.empty()
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
private _logs: string[] = []
@ -75,21 +72,11 @@ export class KclManager {
get programMemory() {
return this._programMemory
}
// This is private because callers should be setting the entire execState.
private set programMemory(programMemory) {
set programMemory(programMemory) {
this._programMemory = programMemory
this._programMemoryCallBack(programMemory)
}
set execState(execState) {
this._execState = execState
this.programMemory = execState.memory
}
get execState() {
return this._execState
}
get logs() {
return this._logs
}
@ -266,9 +253,8 @@ export class KclManager {
// Make sure we clear before starting again. End session will do this.
this.engineCommandManager?.endSession()
await this.ensureWasmInit()
const { logs, errors, execState, isInterrupted } = await executeAst({
const { logs, errors, programMemory, isInterrupted } = await executeAst({
ast,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager,
})
@ -278,7 +264,7 @@ export class KclManager {
this.lints = await lintAst({ ast: ast })
sceneInfra.modelingSend({ type: 'code edit during sketch' })
defaultSelectionFilter(execState.memory, this.engineCommandManager)
defaultSelectionFilter(programMemory, this.engineCommandManager)
if (args.zoomToFit) {
let zoomObjectId: string | undefined = ''
@ -296,7 +282,6 @@ export class KclManager {
type: 'zoom_to_fit',
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
padding: 0.1, // padding around the objects
animated: false, // don't animate the zoom for now
},
})
}
@ -309,20 +294,12 @@ export class KclManager {
this._cancelTokens.delete(currentExecutionId)
return
}
// Exit sketch mode if the AST is empty
if (this._isAstEmpty(ast)) {
await this.disableSketchMode()
}
this.logs = logs
// Do not add the errors since the program was interrupted and the error is not a real KCL error
this.addKclErrors(isInterrupted ? [] : errors)
// Reset the next ID index so that we reuse the previous IDs next time.
execState.idGenerator.nextId = 0
this.execState = execState
this.programMemory = programMemory
if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory
this.lastSuccessfulProgramMemory = programMemory
}
this.ast = { ...ast }
this._executeCallback()
@ -360,19 +337,17 @@ export class KclManager {
await codeManager.writeToFile()
this._ast = { ...newAst }
const { logs, errors, execState } = await executeAst({
const { logs, errors, programMemory } = await executeAst({
ast: newAst,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager,
useFakeExecutor: true,
})
this._logs = logs
this._kclErrors = errors
this._execState = execState
this._programMemory = execState.memory
this._programMemory = programMemory
if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory
this.lastSuccessfulProgramMemory = programMemory
}
if (updates !== 'artifactRanges') return
@ -577,24 +552,6 @@ export class KclManager {
defaultSelectionFilter() {
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(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import {
getConstraintLevelFromSourceRange,
} from './sketchcombos'
import { ToolTip } from 'lang/langHelpers'
import { Selection, Selections } from 'lib/selections'
import { Selections } from 'lib/selections'
import { err } from 'lib/trap'
import { enginelessExecutor } from '../../lib/testHelpers'
@ -96,86 +96,6 @@ function makeSelections(
}
describe('testing transformAstForSketchLines for equal length constraint', () => {
describe(`should always reorder selections to have the base selection first`, () => {
const inputScript = `sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([5, 5], %)
|> line([-2, 5], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`
const expectedModifiedScript = `sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([5, 5], %, $seg01)
|> angledLine([112, segLen(seg01)], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
`
const selectLine = (script: string, lineNumber: number): Selection => {
const lines = script.split('\n')
const codeBeforeLine = lines.slice(0, lineNumber).join('\n').length
const line = lines.find((_, i) => i === lineNumber)
if (!line) {
throw new Error(
`line index ${lineNumber} not found in test sample, friend`
)
}
const start = codeBeforeLine + line.indexOf('|> ' + 5)
const range: [number, number] = [start, start]
return {
type: 'default',
range,
}
}
async function applyTransformation(
inputCode: string,
selectionRanges: Selections['codeBasedSelections']
) {
const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast)
const execState = await enginelessExecutor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)),
ast,
'equalLength'
)
const transformedSelection = makeSelections(selectionRanges)
const newAst = transformSecondarySketchLinesTagFirst({
ast,
selectionRanges: transformedSelection,
transformInfos,
programMemory: execState.memory,
})
if (err(newAst)) return Promise.reject(newAst)
const newCode = recast(newAst.modifiedAst)
return newCode
}
it(`Should reorder when user selects first-to-last`, async () => {
const selectionRanges: Selections['codeBasedSelections'] = [
selectLine(inputScript, 3),
selectLine(inputScript, 4),
]
const newCode = await applyTransformation(inputScript, selectionRanges)
expect(newCode).toBe(expectedModifiedScript)
})
it(`Should reorder when user selects last-to-first`, async () => {
const selectionRanges: Selections['codeBasedSelections'] = [
selectLine(inputScript, 4),
selectLine(inputScript, 3),
]
const newCode = await applyTransformation(inputScript, selectionRanges)
expect(newCode).toBe(expectedModifiedScript)
})
})
const inputScript = `myVar = 3
myVar2 = 5
myVar3 = 6
@ -300,7 +220,7 @@ part001 = startSketchOn('XY')
}
})
const execState = await enginelessExecutor(ast)
const programMemory = await enginelessExecutor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)),
ast,
@ -311,7 +231,7 @@ part001 = startSketchOn('XY')
ast,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory: execState.memory,
programMemory,
})
if (err(newAst)) return Promise.reject(newAst)
@ -391,7 +311,7 @@ part001 = startSketchOn('XY')
}
})
const execState = await enginelessExecutor(ast)
const programMemory = await enginelessExecutor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges),
ast,
@ -402,7 +322,7 @@ part001 = startSketchOn('XY')
ast,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory: execState.memory,
programMemory,
referenceSegName: '',
})
if (err(newAst)) return Promise.reject(newAst)
@ -453,7 +373,7 @@ part001 = startSketchOn('XY')
}
})
const execState = await enginelessExecutor(ast)
const programMemory = await enginelessExecutor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges),
ast,
@ -464,7 +384,7 @@ part001 = startSketchOn('XY')
ast,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory: execState.memory,
programMemory,
referenceSegName: '',
})
if (err(newAst)) return Promise.reject(newAst)
@ -550,7 +470,7 @@ async function helperThing(
}
})
const execState = await enginelessExecutor(ast)
const programMemory = await enginelessExecutor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)),
ast,
@ -561,7 +481,7 @@ async function helperThing(
ast,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory: execState.memory,
programMemory,
})
if (err(newAst)) return Promise.reject(newAst)

View File

@ -1559,15 +1559,7 @@ export function transformSecondarySketchLinesTagFirst({
}
| Error {
// let node = structuredClone(ast)
// We need to sort the selections by their start position
// so that we can process them in dependency order and not write invalid KCL.
const sortedCodeBasedSelections =
selectionRanges.codeBasedSelections.toSorted(
(a, b) => a.range[0] - b.range[0]
)
const primarySelection = sortedCodeBasedSelections[0].range
const secondarySelections = sortedCodeBasedSelections.slice(1)
const primarySelection = selectionRanges.codeBasedSelections[0].range
const _tag = giveSketchFnCallTag(ast, primarySelection, forceSegName)
if (err(_tag)) return _tag
@ -1577,7 +1569,7 @@ export function transformSecondarySketchLinesTagFirst({
ast: modifiedAst,
selectionRanges: {
...selectionRanges,
codeBasedSelections: secondarySelections,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
referencedSegmentRange: primarySelection,
transformInfos,

View File

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

View File

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

View File

@ -281,8 +281,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
multiple: true,
required: true,
skip: false,
warningMessage:
'Fillets cannot touch other fillets yet. This is under development.',
},
radius: {
inputType: 'kcl',

View File

@ -113,7 +113,6 @@ export type CommandArgumentConfig<
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
machineContext?: C
) => boolean)
warningMessage?: string
skip?: boolean
/** For showing a summary display of the current value, such as in
* the command bar's header
@ -190,7 +189,6 @@ export type CommandArgument<
) => boolean)
skip?: boolean
machineActor?: Actor<T>
warningMessage?: string
/** For showing a summary display of the current value, such as in
* the command bar's header
*/

View File

@ -102,6 +102,3 @@ export const KCL_SAMPLES_MANIFEST_URLS = {
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifest.json',
localFallback: '/kcl-samples-manifest-fallback.json',
} as const
/** Toast id for the app auto-updater toast */
export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast'

View File

@ -152,7 +152,6 @@ export function buildCommandArgument<
skip: arg.skip,
machineActor,
valueSummary: arg.valueSummary,
warningMessage: arg.warningMessage ?? '',
} satisfies Omit<CommandArgument<O, T>, 'inputType'>
if (arg.inputType === 'options') {

View File

@ -379,7 +379,7 @@ const getAppFolderName = () => {
return window.electron.packageJson.name
}
export const getAppSettingsFilePath = async () => {
const getAppSettingsFilePath = async () => {
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
const testSettingsPath = window.electron.process.env.TEST_SETTINGS_FILE_KEY
const appConfig = await window.electron.getPath('appData')

View File

@ -145,13 +145,6 @@ export const interactionMap: Record<
description:
'Available while modeling with either a face selected or an empty selection, when not typing in the code editor.',
},
{
name: 'center-on-selection',
sequence: `${PRIMARY}+Alt+C`,
title: 'Center on selection',
description:
'Centers the view on the selected geometry, or everything if nothing is selected.',
},
],
'Code Editor': [
{

View File

@ -177,14 +177,14 @@ export async function loadAndValidateSettings(
if (err(appSettingsPayload)) return Promise.reject(appSettingsPayload)
let settingsNext = createSettings()
const settings = createSettings()
// Because getting the default directory is async, we need to set it after
if (onDesktop) {
settings.app.projectDirectory.default = await getInitialDefaultDir()
}
settingsNext = setSettingsAtLevel(
settingsNext,
setSettingsAtLevel(
settings,
'user',
configurationToSettingsPayload(appSettingsPayload)
)
@ -199,8 +199,8 @@ export async function loadAndValidateSettings(
return Promise.reject(new Error('Invalid project settings'))
const projectSettingsPayload = projectSettings
settingsNext = setSettingsAtLevel(
settingsNext,
setSettingsAtLevel(
settings,
'project',
projectConfigurationToSettingsPayload(projectSettingsPayload)
)
@ -208,7 +208,7 @@ export async function loadAndValidateSettings(
// Return the settings object
return {
settings: settingsNext,
settings,
configuration: appSettingsPayload,
}
}

View File

@ -49,7 +49,6 @@ if (typeof window !== 'undefined') {
type: 'zoom_to_fit',
object_ids: [], // leave empty to zoom to all objects
padding: 0.2, // padding around the objects
animated: false, // don't animate the zoom for now
},
})
}

View File

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

View File

@ -249,7 +249,7 @@ export async function submitAndAwaitTextToKcl({
export async function sendTelemetry(
id: string,
feedback: Models['MlFeedback_type'],
feedback: Models['AiFeedback_type'],
token?: string
): Promise<void> {
const url =

View File

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

View File

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

View File

@ -252,9 +252,6 @@ export type ModelingMachineEvent =
type: 'Set Segment Overlays'
data: SegmentOverlayPayload
}
| {
type: 'Center camera on selection'
}
| {
type: 'Delete segment'
data: PathToNode
@ -666,7 +663,6 @@ export const modelingMachine = setup({
const testExecute = await executeAst({
ast: modifiedAst,
idGenerator: kclManager.execState.idGenerator,
useFakeExecutor: true,
engineCommandManager,
})
@ -942,7 +938,6 @@ export const modelingMachine = setup({
'Set selection': () => {},
'Set mouse state': () => {},
'Set Segment Overlays': () => {},
'Center camera on selection': () => {},
'Engine export': () => {},
'Submit to Text-to-CAD API': () => {},
'Set sketchDetails': () => {},
@ -2110,10 +2105,6 @@ export const modelingMachine = setup({
reenter: false,
actions: 'Set Segment Overlays',
},
'Center camera on selection': {
reenter: false,
actions: 'Center camera on selection',
},
},
})

View File

@ -19,7 +19,7 @@ export const settingsMachine = setup({
types: {
context: {} as ReturnType<typeof createSettings>,
input: {} as ReturnType<typeof createSettings>,
events: {} as (
events: {} as
| WildcardSetEvent<SettingsPaths>
| SetEventTypes
| {
@ -34,8 +34,7 @@ export const settingsMachine = setup({
type: 'Reset settings'
level: SettingsLevel
}
| { type: 'Set all settings'; settings: typeof settings }
) & { doNotPersist?: boolean },
| { type: 'Set all settings'; settings: typeof settings },
},
actions: {
setEngineTheme: () => {},

View File

@ -261,30 +261,10 @@ app.on('ready', () => {
autoUpdater.checkForUpdates().catch(reportRejection)
}, fifteenMinutes)
autoUpdater.on('error', (error) => {
console.error('updater-error', error)
mainWindow?.webContents.send('updater-error', error)
})
autoUpdater.on('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) => {
console.log('update-downloaded', info)
mainWindow?.webContents.send('update-downloaded', info.version)

View File

@ -5,7 +5,6 @@ import os from 'node:os'
import fsSync from 'node:fs'
import packageJson from '../package.json'
import { MachinesListing } from 'lib/machineManager'
import chokidar from 'chokidar'
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args)
@ -16,34 +15,44 @@ const startDeviceFlow = (host: string): Promise<string> =>
ipcRenderer.invoke('startDeviceFlow', host)
const loginWithDeviceFlow = (): Promise<string> =>
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) =>
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 isMac = os.platform() === 'darwin'
const isWindows = os.platform() === 'win32'
const isLinux = os.platform() === 'linux'
let fsWatchListeners = new Map<string, ReturnType<typeof chokidar.watch>>()
let fsWatchListeners = new Map<
string,
{
watcher: fsSync.FSWatcher
callback: (eventType: string, path: string) => void
}
>()
const watchFileOn = (path: string, callback: (path: string) => void) => {
const watcherMaybe = fsWatchListeners.get(path)
if (watcherMaybe) return
const watcher = chokidar.watch(path)
watcher.on('all', callback)
fsWatchListeners.set(path, watcher)
const watchFileOn = (
path: string,
callback: (eventType: string, path: string) => void
) => {
const watcher = fsSync.watch(path)
watcher.on('change', callback)
fsWatchListeners.set(path, { watcher, callback })
}
const watchFileOff = (path: string) => {
const watcher = fsWatchListeners.get(path)
if (!watcher) return
watcher.unwatch(path)
const entry = fsWatchListeners.get(path)
if (!entry) return
const { watcher, callback } = entry
watcher.off('change', callback)
watcher.close()
fsWatchListeners.delete(path)
}
const watchFileObliterate = () => {
for (let [pathAsKey] of fsWatchListeners) {
watchFileOff(pathAsKey)
}
fsWatchListeners = new Map()
}
const readFile = (path: string) => fs.readFile(path, 'utf-8')
// It seems like from the node source code this does not actually block but also
// don't trust me on that (jess).
@ -94,6 +103,7 @@ contextBridge.exposeInMainWorld('electron', {
// exported.
watchFileOn,
watchFileOff,
watchFileObliterate,
readFile,
writeFile,
exists,
@ -149,8 +159,6 @@ contextBridge.exposeInMainWorld('electron', {
kittycad,
listMachines,
getMachineApiIp,
onUpdateDownloadStart,
onUpdateDownloaded,
onUpdateError,
appRestart,
})

View File

@ -176,7 +176,7 @@ const Home = () => {
// Re-read projects listing if the projectDir has any updates.
useFileSystemWatcher(
async () => {
() => {
setProjectsLoaderTrigger(projectsLoaderTrigger + 1)
},
projectsDir ? [projectsDir] : []

View File

@ -434,9 +434,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.20"
version = "4.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615"
dependencies = [
"clap_builder",
"clap_derive",
@ -444,9 +444,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.20"
version = "4.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b"
dependencies = [
"anstream",
"anstyle",
@ -934,9 +934,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.31"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
@ -949,9 +949,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.31"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
@ -959,15 +959,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.31"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
version = "0.3.31"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
@ -976,15 +976,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.31"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
version = "0.3.31"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
@ -993,21 +993,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.31"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.31"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.31"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
@ -1533,16 +1533,16 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.71"
version = "0.3.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b"
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kcl-lib"
version = "0.2.21"
version = "0.2.20"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1966,9 +1966,12 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.20.2"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
dependencies = [
"portable-atomic",
]
[[package]]
name = "oncemutex"
@ -3907,9 +3910,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.94"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887"
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
dependencies = [
"cfg-if",
"once_cell",
@ -3918,9 +3921,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.94"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e"
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
dependencies = [
"bumpalo",
"log",
@ -3933,9 +3936,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.44"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65471f79c1022ffa5291d33520cbbb53b7687b01c2f8e83b57d102eed7ed479d"
checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
dependencies = [
"cfg-if",
"futures-core",
@ -3946,9 +3949,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.94"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7"
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -3956,9 +3959,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.94"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6"
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
@ -3969,9 +3972,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.94"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9"
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
[[package]]
name = "wasm-lib"

View File

@ -20,7 +20,7 @@ tokio = { version = "1.40.0", features = ["sync"] }
toml = "0.8.19"
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
wasm-bindgen = "0.2.91"
wasm-bindgen-futures = "0.4.44"
wasm-bindgen-futures = "0.4.42"
[dev-dependencies]
anyhow = "1"
@ -35,10 +35,10 @@ uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.31"
js-sys = "0.3.71"
futures = "0.3.30"
js-sys = "0.3.69"
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
wasm-bindgen-futures = { version = "0.4.44", features = ["futures-core-03-stream"] }
wasm-bindgen-futures = { version = "0.4.41", features = ["futures-core-03-stream"] }
wasm-streams = "0.4.1"
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]

View File

@ -14,7 +14,7 @@ proc-macro = true
[dependencies]
Inflector = "0.11.4"
convert_case = "0.6.0"
once_cell = "1.20.2"
once_cell = "1.19.0"
proc-macro2 = "1"
quote = "1"
regex = "1.10"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -166,19 +166,15 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
Err(e) => return bad_request(format!("Parse error: {e}")),
};
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.
// There's no actual KCL associated with this reset_scene call.
if let Err(e) = state
.reset_scene(&mut id_generator, kcl_lib::executor::SourceRange::default())
.await
{
if let Err(e) = state.reset_scene(kcl_lib::executor::SourceRange::default()).await {
return kcl_err(e);
}
// Let users know if the test is taking a long time.
let (done_tx, done_rx) = oneshot::channel::<()>();
let timer = time_until(done_rx);
let snapshot = match state.execute_and_prepare_snapshot(&program, id_generator).await {
let snapshot = match state.execute_and_prepare_snapshot(&program).await {
Ok(sn) => sn,
Err(e) => return kcl_err(e),
};

View File

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

View File

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

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.21"
version = "0.2.20"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -16,13 +16,13 @@ async-recursion = "1.1.1"
async-trait = "0.1.83"
base64 = "0.22.1"
chrono = "0.4.38"
clap = { version = "4.5.20", default-features = false, optional = true, features = ["std", "derive"] }
clap = { version = "4.5.19", default-features = false, optional = true, features = ["std", "derive"] }
convert_case = "0.6.0"
dashmap = "6.1.0"
databake = { version = "0.1.8", features = ["derive"] }
derive-docs = { version = "0.1.29", path = "../derive-docs" }
form_urlencoded = "1.2.1"
futures = { version = "0.3.31" }
futures = { version = "0.3.30" }
git_rev = "0.1.0"
gltf-json = "1.4.1"
http = { workspace = true }
@ -53,11 +53,11 @@ winnow = "0.6.18"
zip = { version = "2.0.0", default-features = false }
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = { version = "0.3.71" }
js-sys = { version = "0.3.69" }
tokio = { version = "1.40.0", features = ["sync", "time"] }
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
wasm-bindgen = "0.2.91"
wasm-bindgen-futures = "0.4.44"
wasm-bindgen-futures = "0.4.42"
web-sys = { version = "0.3.69", features = ["console"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View File

@ -787,14 +787,12 @@ fn test_generate_stdlib_json_schema() {
let stdlib = StdLib::new();
let combined = stdlib.combined();
let json_data: Vec<_> = combined
.keys()
.sorted()
.map(|key| {
let internal_fn = combined.get(key).unwrap();
internal_fn.to_json().unwrap()
})
.collect();
let mut json_data = vec![];
for key in combined.keys().sorted() {
let internal_fn = combined.get(key).unwrap();
json_data.push(internal_fn.to_json().unwrap());
}
expectorate::assert_contents(
"../../../docs/kcl/std.json",
&serde_json::to_string_pretty(&json_data).unwrap(),

View File

@ -83,8 +83,6 @@ impl StdLibFnArg {
return Ok(Some((index, format!("${{{}:{}}}", index, "myTag"))));
} else if self.type_ == "[KclValue]" && self.required {
return Ok(Some((index, format!("${{{}:{}}}", index, "[0..9]"))));
} else if self.type_ == "KclValue" && self.required {
return Ok(Some((index, format!("${{{}:{}}}", index, "3"))));
}
get_autocomplete_snippet_from_schema(&self.schema.schema.clone().into(), index)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -40,8 +40,6 @@ use crate::{
pub struct ExecState {
/// Program variable bindings.
pub memory: ProgramMemory,
/// The stable artifact ID generator.
pub id_generator: IdGenerator,
/// Dynamic state that follows dynamic flow of the program.
pub dynamic_state: DynamicState,
/// The current value of the pipe operator returned from the previous
@ -294,34 +292,7 @@ impl DynamicState {
}
}
/// 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.
/// A memory item.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
@ -628,82 +599,6 @@ pub struct Plane {
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)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
@ -1941,12 +1836,8 @@ impl ExecutorContext {
Ok(ctx)
}
pub async fn reset_scene(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
) -> Result<()> {
self.engine.clear_scene(id_generator, source_range).await?;
pub async fn reset_scene(&self, source_range: crate::executor::SourceRange) -> Result<()> {
self.engine.clear_scene(source_range).await?;
Ok(())
}
@ -1957,11 +1848,8 @@ impl ExecutorContext {
&self,
program: &crate::ast::types::Program,
memory: Option<ProgramMemory>,
id_generator: IdGenerator,
) -> Result<ExecState, KclError> {
self.run_with_session_data(program, memory, id_generator)
.await
.map(|x| x.0)
self.run_with_session_data(program, memory).await.map(|x| x.0)
}
/// Perform the execution of a program.
/// You can optionally pass in some initialization memory.
@ -1970,22 +1858,11 @@ impl ExecutorContext {
&self,
program: &crate::ast::types::Program,
memory: Option<ProgramMemory>,
id_generator: IdGenerator,
) -> 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.
self.engine
.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
uuid::Uuid::new_v4(),
SourceRange::default(),
&ModelingCmd::from(mcmd::SetSceneUnits {
unit: match self.settings.units {
@ -1999,7 +1876,15 @@ impl ExecutorContext {
}),
)
.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)
.await?;
let session_data = self.engine.get_session_data();
@ -2144,12 +2029,8 @@ impl ExecutorContext {
}
/// Execute the program, then get a PNG screenshot.
pub async fn execute_and_prepare_snapshot(
&self,
program: &Program,
id_generator: IdGenerator,
) -> Result<TakeSnapshot> {
let _ = self.run(program, None, id_generator).await?;
pub async fn execute_and_prepare_snapshot(&self, program: &Program) -> Result<TakeSnapshot> {
let _ = self.run(program, None).await?;
// Zoom to fit.
self.engine
@ -2294,7 +2175,7 @@ mod tests {
settings: Default::default(),
context_type: ContextType::Mock,
};
let exec_state = ctx.run(&program, None, IdGenerator::default()).await?;
let exec_state = ctx.run(&program, None).await?;
Ok(exec_state.memory)
}

View File

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

View File

@ -4,7 +4,7 @@ use serde_json::Value as JValue;
use super::{args::FromArgs, Args, FnAsArg};
use crate::{
errors::{KclError, KclErrorDetails},
executor::{ExecState, KclValue, SourceRange, UserVal},
executor::{ExecState, KclValue, Sketch, SourceRange, UserVal},
function_param::FunctionParam,
};
@ -98,16 +98,7 @@ async fn call_map_closure<'a>(
/// For each item in an array, update a value.
pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (array, start, f): (Vec<JValue>, KclValue, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let array: Vec<KclValue> = array
.into_iter()
.map(|jval| {
KclValue::UserVal(UserVal {
value: jval,
meta: vec![args.source_range.into()],
})
})
.collect();
let (array, start, f): (Vec<u64>, Sketch, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let reduce_fn = FunctionParam {
inner: f.func,
fn_expr: f.expr,
@ -115,7 +106,9 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
ctx: args.ctx.clone(),
memory: *f.memory,
};
inner_reduce(array, start, reduce_fn, exec_state, &args).await
inner_reduce(array, start, reduce_fn, exec_state, &args)
.await
.map(|sg| KclValue::UserVal(UserVal::new(sg.meta.clone(), sg)))
}
/// Take a starting value. Then, for each element of an array, calculate the next value,
@ -132,52 +125,60 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// }
/// decagon(5.0) |> close(%)
/// ```
/// ```no_run
/// array = [1, 2, 3]
/// sum = reduce(array, 0, (i, result_so_far) => { return i + result_so_far })
/// assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
/// ```
/// ```no_run
/// fn add = (a, b) => { return a + b }
/// fn sum = (array) => { return reduce(array, 0, add) }
/// assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
/// ```
#[stdlib {
name = "reduce",
}]
async fn inner_reduce<'a>(
array: Vec<KclValue>,
start: KclValue,
array: Vec<u64>,
start: Sketch,
reduce_fn: FunctionParam<'a>,
exec_state: &mut ExecState,
args: &'a Args,
) -> Result<KclValue, KclError> {
) -> Result<Sketch, KclError> {
let mut reduced = start;
for elem in array {
reduced = call_reduce_closure(elem, reduced, &reduce_fn, args.source_range, exec_state).await?;
for i in array {
reduced = call_reduce_closure(i, reduced, &reduce_fn, args.source_range, exec_state).await?;
}
Ok(reduced)
}
async fn call_reduce_closure<'a>(
elem: KclValue,
start: KclValue,
i: u64,
start: Sketch,
reduce_fn: &FunctionParam<'a>,
source_range: SourceRange,
exec_state: &mut ExecState,
) -> Result<KclValue, KclError> {
) -> Result<Sketch, KclError> {
// Call the reduce fn for this repetition.
let reduce_fn_args = vec![elem, start];
let reduce_fn_args = vec![
KclValue::UserVal(UserVal {
value: serde_json::Value::Number(i.into()),
meta: vec![source_range.into()],
}),
KclValue::new_user_val(start.meta.clone(), start),
];
let transform_fn_return = reduce_fn.call(exec_state, reduce_fn_args).await?;
// Unpack the returned transform object.
let source_ranges = vec![source_range];
let out = transform_fn_return.ok_or_else(|| {
let closure_retval = transform_fn_return.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: "Reducer function must return a value".to_string(),
source_ranges: source_ranges.clone(),
})
})?;
let Some(out) = closure_retval.as_user_val() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Reducer function must return a UserValue".to_string(),
source_ranges: source_ranges.clone(),
}));
};
let Some((out, _meta)) = out.get() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Reducer function must return a Sketch".to_string(),
source_ranges: source_ranges.clone(),
}));
};
Ok(out)
}

View File

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

View File

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

View File

@ -142,7 +142,7 @@ async fn inner_fillet(
for edge_tag in data.tags {
let edge_id = edge_tag.get_engine_id(exec_state, &args)?;
let id = exec_state.id_generator.next_uuid();
let id = uuid::Uuid::new_v4();
args.batch_end_cmd(
id,
ModelingCmd::from(mcmd::Solid3dFilletEdge {
@ -229,16 +229,15 @@ 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> {
if args.ctx.is_mock() {
return Ok(exec_state.id_generator.next_uuid());
return Ok(Uuid::new_v4());
}
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 resp = args
.send_modeling_cmd(
id,
uuid::Uuid::new_v4(),
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
edge_id: tagged_path.id,
object_id: tagged_path.sketch,
@ -311,16 +310,15 @@ async fn inner_get_next_adjacent_edge(
args: Args,
) -> Result<Uuid, KclError> {
if args.ctx.is_mock() {
return Ok(exec_state.id_generator.next_uuid());
return Ok(Uuid::new_v4());
}
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 resp = args
.send_modeling_cmd(
id,
uuid::Uuid::new_v4(),
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
edge_id: tagged_path.id,
object_id: tagged_path.sketch,
@ -401,16 +399,15 @@ async fn inner_get_previous_adjacent_edge(
args: Args,
) -> Result<Uuid, KclError> {
if args.ctx.is_mock() {
return Ok(exec_state.id_generator.next_uuid());
return Ok(Uuid::new_v4());
}
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 resp = args
.send_modeling_cmd(
id,
uuid::Uuid::new_v4(),
ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge {
edge_id: tagged_path.id,
object_id: tagged_path.sketch,

View File

@ -32,10 +32,10 @@ pub struct HelixData {
}
/// 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 solid = inner_helix(data, solid, exec_state, args).await?;
let solid = inner_helix(data, solid, args).await?;
Ok(KclValue::Solid(solid))
}
@ -54,13 +54,8 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
#[stdlib {
name = "helix",
}]
async fn inner_helix(
data: HelixData,
solid: Box<Solid>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
let id = exec_state.id_generator.next_uuid();
async fn inner_helix(data: HelixData, solid: Box<Solid>, args: Args) -> Result<Box<Solid>, KclError> {
let id = uuid::Uuid::new_v4();
args.batch_modeling_cmd(
id,
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
/// 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 imported_geometry = inner_import(file_path, options, exec_state, args).await?;
let imported_geometry = inner_import(file_path, options, args).await?;
Ok(KclValue::ImportedGeometry(imported_geometry))
}
@ -170,7 +170,6 @@ pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
async fn inner_import(
file_path: String,
options: Option<ImportFormat>,
exec_state: &mut ExecState,
args: Args,
) -> Result<ImportedGeometry, KclError> {
if file_path.is_empty() {
@ -287,13 +286,13 @@ async fn inner_import(
if args.ctx.is_mock() {
return Ok(ImportedGeometry {
id: exec_state.id_generator.next_uuid(),
id: uuid::Uuid::new_v4(),
value: import_files.iter().map(|f| f.path.to_string()).collect(),
meta: vec![args.source_range.into()],
});
}
let id = exec_state.id_generator.next_uuid();
let id = uuid::Uuid::new_v4();
let resp = args
.send_modeling_cmd(
id,

Some files were not shown because too many files have changed in this diff Show More