Compare commits

...

25 Commits

Author SHA1 Message Date
4b8ce34b31 Fix release notes (#4295) 2024-10-24 17:07:12 -04:00
6617f72373 Fix docs for how to patch kcmc (#4294) 2024-10-24 20:14:38 +00:00
e9033e1754 Cut release v0.26.1 (#4289) 2024-10-24 14:08:29 -04:00
9b697e30cf Revert "Separate debug/release electron-builder to help mac job (#4270)" (#4293)
This reverts commit 19d01c563e.
2024-10-24 13:07:43 -04:00
a70facdab4 Disable rotate on start new sketch (#4287)
disable rotate on start new sketch

Co-authored-by: Frank Noirot <frank@zoo.dev>
2024-10-24 10:18:41 -04:00
4083f9f3dd Fix release notes in last_download.json (#4247)
* Fix release notes in last_download.json
Fixes #4246

* Fix job output

* Clean up, ready for merge
2024-10-24 09:47:44 -04:00
7ead2bb875 Stop propagation of camera clicks after drags (#4257)
* Update CameraControls.ts

* fix static analyzer error

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)"

This reverts commit 0b63016217.

* Don't perform sketch click operations if a camera movement interaction also matches

* Don't `stopPropogation`, make selection listener early return if `wasDragging`
+ consolidate `wasDragging` set statements, add comments

* Codespell

---------

Co-authored-by: 49fl <ircsurfer33@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Frank Noirot <frank@zoo.dev>
2024-10-24 09:26:33 -04:00
19d01c563e Separate debug/release electron-builder to help mac job (#4270)
* Separate debug/release electron-builder to help mac job
Will attempt to fix #4199

* Test BUILD_RELEASE: true

* Revert "Test BUILD_RELEASE: true"

This reverts commit f2c0c24432.
2024-10-24 08:10:16 -04:00
dfe7cfc91c Bump syn from 2.0.82 to 2.0.85 in /src/wasm-lib (#4283)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.82 to 2.0.85.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.82...2.0.85)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 05:52:18 +00:00
01443e445d Bump kittycad-modeling-cmds from 0.2.68 to 0.2.70 in /src/wasm-lib (#4284)
Bumps [kittycad-modeling-cmds](https://github.com/KittyCAD/modeling-api) from 0.2.68 to 0.2.70.
- [Commits](https://github.com/KittyCAD/modeling-api/compare/kittycad-modeling-cmds-0.2.68...kittycad-modeling-cmds-0.2.70)

---
updated-dependencies:
- dependency-name: kittycad-modeling-cmds
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 03:53:07 +00:00
e16eb49f51 Bump serde from 1.0.210 to 1.0.213 in /src/wasm-lib (#4285)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.210 to 1.0.213.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.210...v1.0.213)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 03:50:38 +00:00
5d5138e8e6 Bump syn from 2.0.79 to 2.0.82 in /src/wasm-lib (#4252)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.79 to 2.0.82.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.79...2.0.82)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-10-23 23:48:18 +00:00
e1d6e29523 Update machine-api spec (#4241)
* YOYO NEW API SPEC!

* New machine-api types

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-10-23 23:41:48 +00:00
49657ad2e5 Bump @types/node from 22.5.0 to 22.7.8 (#4258)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.5.0 to 22.7.8.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 15:12:40 -07:00
b40d353994 Bump thiserror from 1.0.64 to 1.0.65 in /src/wasm-lib (#4268)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.64 to 1.0.65.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.64...1.0.65)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 21:47:08 +00:00
62ffa53add Bump proc-macro2 from 1.0.88 to 1.0.89 in /src/wasm-lib (#4266)
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.88 to 1.0.89.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.88...1.0.89)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 21:46:23 +00:00
64dce4d8b1 Bump anyhow from 1.0.89 to 1.0.91 in /src/wasm-lib (#4269)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.89 to 1.0.91.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.89...1.0.91)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 21:41:00 +00:00
02588b2672 Rename Sketch.value to Sketch.paths (#4272)
'value' is not a useful name
2024-10-23 17:42:54 +00:00
3d1ac2ac0b Fix engine connection break when starting onboarding from a fresh install (#4263)
* Make electron test setting overrides not entirely replace default settings

* Add failing test

* Fix test by checking for healthy engine connection before executing demo code

* Fix one electron test that assumed all settings got wiped if you override any.

* 🤷🏻‍♂️ an engine-side camera position in one of the E2E tests changed by 0.01 randomly
2024-10-22 20:22:52 -04:00
ff5ce29fd7 Delete ..env.development.local.swp (#4259) 2024-10-22 18:33:28 -04:00
4bd7e02271 Support negative start and end in ranges (#4249)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-10-22 14:28:48 +13:00
26042790b6 Remove setInterval implementations from camera controls (#4255)
* remove setInterval implementations from camera controls

* fmt
2024-10-21 16:46:47 -07:00
af74f3bb05 Upgrade to rust toolchain 1.82.0 (#4245)
* Upgrade to rust toolchain 1.82.0

* Fix lint about variant being too large

* Fix lint about Err variant being too large
2024-10-21 16:35:04 -05:00
0bdedf5854 fix job name for printers (#4234)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Paul Tagliamonte <paul@zoo.dev>
2024-10-21 21:27:32 +00:00
d2c6b5cf3a Buffer file writes, because writing to file after every keypress has bad consequences (#4242)
* Buffer file writes, because writing to file after every keypress does bad things

* fix: kevin -- added timeouts for the time being since the workflow for saving data to disk is now changed

---------

Co-authored-by: Kevin Nadro <kevin@zoo.dev>
2024-10-21 15:07:20 -05:00
51 changed files with 1135 additions and 919 deletions

View File

@ -15,6 +15,7 @@ on:
env: env:
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }} CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }} BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -25,7 +26,6 @@ jobs:
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows) runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
outputs: outputs:
version: ${{ steps.export_version.outputs.version }} version: ${{ steps.export_version.outputs.version }}
notes: ${{ steps.export_version.outputs.notes }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -55,8 +55,6 @@ jobs:
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json # 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 - name: Generate release notes
env:
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
run: | run: |
echo "$NOTES" > release-notes.md echo "$NOTES" > release-notes.md
cat release-notes.md cat release-notes.md
@ -84,9 +82,6 @@ jobs:
path: | path: |
electron-builder.yml electron-builder.yml
- id: export_notes
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
- name: Prepare electron-builder.yml file for updater test - name: Prepare electron-builder.yml file for updater test
if: ${{ env.CUT_RELEASE_PR == 'true' }} if: ${{ env.CUT_RELEASE_PR == 'true' }}
run: | run: |
@ -262,7 +257,6 @@ jobs:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} 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) }} 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 }} PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
NOTES: ${{ needs.prepare-files.outputs.notes }}
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }} 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' }} 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' }} URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}

File diff suppressed because it is too large Load Diff

View File

@ -16,8 +16,8 @@ A sketch is a collection of paths.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No | | `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No | | `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |

View File

@ -25,8 +25,8 @@ A sketch is a collection of paths.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `sketch`| | No | | `type` |enum: `sketch`| | No |
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No | | `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No | | `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |

View File

@ -136,6 +136,9 @@ test.describe('when using the file tree to', () => {
) )
await pasteCodeInEditor(kclCube) await pasteCodeInEditor(kclCube)
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
await tronApp.page.waitForTimeout(2000)
await renameFile(fromFile, toFile) await renameFile(fromFile, toFile)
await tronApp.page.reload() await tronApp.page.reload()
@ -222,9 +225,11 @@ test.describe('when using the file tree to', () => {
) )
await pasteCodeInEditor(kclCube) await pasteCodeInEditor(kclCube)
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
await tronApp.page.waitForTimeout(2000)
const kcl1 = 'main.kcl' const kcl1 = 'main.kcl'
const kcl2 = '2.kcl' const kcl2 = '2.kcl'
await createNewFileAndSelect(kcl2) await createNewFileAndSelect(kcl2)
const kclCylinder = await fsp.readFile( const kclCylinder = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cylinder.kcl', 'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
@ -232,6 +237,9 @@ test.describe('when using the file tree to', () => {
) )
await pasteCodeInEditor(kclCylinder) await pasteCodeInEditor(kclCylinder)
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
await tronApp.page.waitForTimeout(2000)
await renameFile(kcl2, kcl1) await renameFile(kcl2, kcl1)
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => { await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {

View File

@ -55,6 +55,53 @@ test.describe('Onboarding tests', () => {
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
}) })
test(
'Desktop: fresh onboarding executes and loads',
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
appSettings: {
app: {
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
})
const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 }
await page.setViewportSize(viewportSize)
// Locators and constants
const newProjectButton = page.getByRole('button', { name: 'New project' })
const projectLink = page.getByTestId('project-link')
await test.step(`Create a project and open to the onboarding`, async () => {
await newProjectButton.click()
await projectLink.click()
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
await u.waitForPageLoad()
})
})
await test.step(`Ensure we see the onboarding stuff`, async () => {
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
// *and* that the code is shown in the editor
await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
})
await electronApp.close()
}
)
test('Code resets after confirmation', async ({ page }) => { test('Code resets after confirmation', async ({ page }) => {
const initialCode = `sketch001 = startSketchOn('XZ')` const initialCode = `sketch001 = startSketchOn('XZ')`

View File

@ -888,7 +888,17 @@ export async function setupElectron({
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
const settingsOverrides = TOML.stringify( const settingsOverrides = TOML.stringify(
appSettings appSettings
? { settings: appSettings } ? {
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
projectDirectory: projectDirName,
...appSettings.app,
},
},
}
: { : {
settings: { settings: {
...TEST_SETTINGS, ...TEST_SETTINGS,

View File

@ -292,7 +292,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
await test.step(`Verify the camera moved`, async () => { await test.step(`Verify the camera moved`, async () => {
await scene.expectState({ await scene.expectState({
camera: { camera: {
position: [0, -23865.37, 11073.54], position: [0, -23865.37, 11073.53],
target: [0, 0, 0], target: [0, 0, 0],
}, },
}) })

View File

@ -430,7 +430,6 @@ test.describe('Testing settings', () => {
await test.step('Check color of logo changed when in modeling view', async () => { await test.step('Check color of logo changed when in modeling view', async () => {
await page.getByRole('button', { name: 'New project' }).click() await page.getByRole('button', { name: 'New project' }).click()
await page.getByTestId('project-link').first().click() await page.getByTestId('project-link').first().click()
await page.getByRole('button', { name: 'Dismiss' }).click()
await changeColor('58') await changeColor('58')
await expect(logoLink).toHaveCSS('--primary-hue', '58') await expect(logoLink).toHaveCSS('--primary-hue', '58')
}) })

View File

@ -107,6 +107,13 @@
}, },
"type": "array" "type": "array"
}, },
"loaded_filament_idx": {
"description": "The currently loaded filament index.",
"format": "uint",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"nozzle_diameter": { "nozzle_diameter": {
"description": "Diameter of the extrusion nozzle, in mm.", "description": "Diameter of the extrusion nozzle, in mm.",
"format": "double", "format": "double",
@ -285,6 +292,21 @@
"type" "type"
], ],
"type": "object" "type": "object"
},
{
"description": "Unknown material",
"properties": {
"type": {
"enum": [
"unknown"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
} }
] ]
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "zoo-modeling-app", "name": "zoo-modeling-app",
"version": "0.26.0", "version": "0.26.1",
"private": true, "private": true,
"productName": "Zoo Modeling App", "productName": "Zoo Modeling App",
"author": { "author": {
@ -161,7 +161,7 @@
"@types/isomorphic-fetch": "^0.0.39", "@types/isomorphic-fetch": "^0.0.39",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/mocha": "^10.0.6", "@types/mocha": "^10.0.6",
"@types/node": "^22.5.0", "@types/node": "^22.7.8",
"@types/pixelmatch": "^5.2.6", "@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/react": "^18.3.4", "@types/react": "^18.3.4",

View File

@ -64,6 +64,27 @@ export type ReactCameraProperties =
const lastCmdDelay = 50 const lastCmdDelay = 50
class CameraRateLimiter {
lastSend?: Date = undefined
rateLimitMs: number = 16 //60 FPS
send = (f: () => void) => {
let now = new Date()
if (
this.lastSend === undefined ||
now.getTime() - this.lastSend.getTime() > this.rateLimitMs
) {
f()
this.lastSend = now
}
}
reset = () => {
this.lastSend = undefined
}
}
export class CameraControls { export class CameraControls {
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient' syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
@ -71,15 +92,15 @@ export class CameraControls {
target: Vector3 target: Vector3
domElement: HTMLCanvasElement domElement: HTMLCanvasElement
isDragging: boolean isDragging: boolean
wasDragging: boolean
mouseDownPosition: Vector2 mouseDownPosition: Vector2
mouseNewPosition: Vector2 mouseNewPosition: Vector2
rotationSpeed = 0.3 rotationSpeed = 0.3
enableRotate = true enableRotate = true
enablePan = true enablePan = true
enableZoom = true enableZoom = true
zoomDataFromLastFrame?: number = undefined moveSender: CameraRateLimiter = new CameraRateLimiter()
// holds coordinates, and interaction zoomSender: CameraRateLimiter = new CameraRateLimiter()
moveDataFromLastFrame?: [number, number, string] = undefined
lastPerspectiveFov: number = 45 lastPerspectiveFov: number = 45
pendingZoom: number | null = null pendingZoom: number | null = null
pendingRotation: Vector2 | null = null pendingRotation: Vector2 | null = null
@ -171,6 +192,36 @@ export class CameraControls {
} }
} }
doMove = (interaction: any, coordinates: any) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_move',
interaction: interaction,
window: {
x: coordinates[0],
y: coordinates[1],
},
},
cmd_id: uuidv4(),
})
}
doZoom = (zoom: number) => {
this.handleStart()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
magnitude: (-1 * zoom) / window.devicePixelRatio,
},
cmd_id: uuidv4(),
})
this.handleEnd()
}
constructor( constructor(
isOrtho = false, isOrtho = false,
domElement: HTMLCanvasElement, domElement: HTMLCanvasElement,
@ -183,6 +234,7 @@ export class CameraControls {
this.target = new Vector3() this.target = new Vector3()
this.domElement = domElement this.domElement = domElement
this.isDragging = false this.isDragging = false
this.wasDragging = false
this.mouseDownPosition = new Vector2() this.mouseDownPosition = new Vector2()
this.mouseNewPosition = new Vector2() this.mouseNewPosition = new Vector2()
@ -258,49 +310,6 @@ export class CameraControls {
this.onCameraChange() this.onCameraChange()
} }
// Our stream is never more than 60fps.
// We can get away with capping our "virtual fps" to 60 then.
const FPS_VIRTUAL = 60
const doZoom = () => {
if (this.zoomDataFromLastFrame !== undefined) {
this.handleStart()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
magnitude:
(-1 * this.zoomDataFromLastFrame) / window.devicePixelRatio,
},
cmd_id: uuidv4(),
})
this.handleEnd()
}
this.zoomDataFromLastFrame = undefined
}
setInterval(doZoom, 1000 / FPS_VIRTUAL)
const doMove = () => {
if (this.moveDataFromLastFrame !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_move',
interaction: this.moveDataFromLastFrame[2] as any,
window: {
x: this.moveDataFromLastFrame[0],
y: this.moveDataFromLastFrame[1],
},
},
cmd_id: uuidv4(),
})
}
this.moveDataFromLastFrame = undefined
}
setInterval(doMove, 1000 / FPS_VIRTUAL)
setTimeout(() => { setTimeout(() => {
this.engineCommandManager.subscribeTo({ this.engineCommandManager.subscribeTo({
event: 'camera_drag_end', event: 'camera_drag_end',
@ -356,6 +365,8 @@ export class CameraControls {
onMouseDown = (event: PointerEvent) => { onMouseDown = (event: PointerEvent) => {
this.domElement.setPointerCapture(event.pointerId) this.domElement.setPointerCapture(event.pointerId)
this.isDragging = true this.isDragging = true
// Reset the wasDragging flag to false when starting a new drag
this.wasDragging = false
this.mouseDownPosition.set(event.clientX, event.clientY) this.mouseDownPosition.set(event.clientX, event.clientY)
let interaction = this.getInteractionType(event) let interaction = this.getInteractionType(event)
if (interaction === 'none') return if (interaction === 'none') return
@ -385,11 +396,18 @@ export class CameraControls {
const interaction = this.getInteractionType(event) const interaction = this.getInteractionType(event)
if (interaction === 'none') return if (interaction === 'none') return
// If there's a valid interaction and the mouse is moving,
// our past (and current) interaction was a drag.
this.wasDragging = true
if (this.syncDirection === 'engineToClient') { if (this.syncDirection === 'engineToClient') {
this.moveDataFromLastFrame = [event.clientX, event.clientY, interaction] this.moveSender.send(() => {
this.doMove(interaction, [event.clientX, event.clientY])
})
return return
} }
// else "clientToEngine" (Sketch Mode) or forceUpdate
// Implement camera movement logic here based on deltaMove // Implement camera movement logic here based on deltaMove
// For example, for rotating the camera around the target: // For example, for rotating the camera around the target:
if (interaction === 'rotate') { if (interaction === 'rotate') {
@ -418,6 +436,9 @@ export class CameraControls {
* under the cursor. This recently moved from being handled in App.tsx. * under the cursor. This recently moved from being handled in App.tsx.
* This might not be the right spot, but it is more consolidated. * This might not be the right spot, but it is more consolidated.
*/ */
// Clear any previous drag state
this.wasDragging = false
if (this.syncDirection === 'engineToClient') { if (this.syncDirection === 'engineToClient') {
const newCmdId = uuidv4() const newCmdId = uuidv4()
@ -459,7 +480,9 @@ export class CameraControls {
if (this.syncDirection === 'engineToClient') { if (this.syncDirection === 'engineToClient') {
if (interaction === 'zoom') { if (interaction === 'zoom') {
this.zoomDataFromLastFrame = event.deltaY this.zoomSender.send(() => {
this.doZoom(event.deltaY)
})
} else { } else {
// This case will get handled when we add pan and rotate using Apple trackpad. // This case will get handled when we add pan and rotate using Apple trackpad.
console.error( console.error(

View File

@ -338,6 +338,11 @@ export class SceneEntities {
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onClick: async (args) => { onClick: async (args) => {
if (!args) return if (!args) return
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args.mouseEvent.which !== 1) return if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args const { intersectionPoint } = args
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return
@ -407,7 +412,7 @@ export class SceneEntities {
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
if (!sketch) return Promise.reject('sketch not found') if (!sketch) return Promise.reject('sketch not found')
if (!isArray(sketch?.value)) if (!isArray(sketch?.paths))
return { return {
truncatedAst, truncatedAst,
programMemoryOverride, programMemoryOverride,
@ -435,7 +440,7 @@ export class SceneEntities {
maybeModdedAst, maybeModdedAst,
sketch.start.__geoMeta.sourceRange sketch.start.__geoMeta.sourceRange
) )
if (sketch?.value?.[0]?.type !== 'Circle') { if (sketch?.paths?.[0]?.type !== 'Circle') {
const _profileStart = createProfileStartHandle({ const _profileStart = createProfileStartHandle({
from: sketch.start.from, from: sketch.start.from,
id: sketch.start.__geoMeta.id, id: sketch.start.__geoMeta.id,
@ -451,16 +456,16 @@ export class SceneEntities {
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
} }
const callbacks: (() => SegmentOverlayPayload | null)[] = [] const callbacks: (() => SegmentOverlayPayload | null)[] = []
sketch.value.forEach((segment, index) => { sketch.paths.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange( let segPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
segment.__geoMeta.sourceRange segment.__geoMeta.sourceRange
) )
if ( if (
draftExpressionsIndices && draftExpressionsIndices &&
(sketch.value[index - 1] || sketch.start) (sketch.paths[index - 1] || sketch.start)
) { ) {
const previousSegment = sketch.value[index - 1] || sketch.start const previousSegment = sketch.paths[index - 1] || sketch.start
const previousSegmentPathToNode = getNodePathFromSourceRange( const previousSegmentPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
previousSegment.__geoMeta.sourceRange previousSegment.__geoMeta.sourceRange
@ -511,7 +516,7 @@ export class SceneEntities {
to: segment.to, to: segment.to,
} }
const result = initSegment({ const result = initSegment({
prevSegment: sketch.value[index - 1], prevSegment: sketch.paths[index - 1],
callExpName, callExpName,
input, input,
id: segment.__geoMeta.id, id: segment.__geoMeta.id,
@ -610,9 +615,9 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return Promise.reject(sg) if (err(sg)) return Promise.reject(sg)
const lastSeg = sg?.value?.slice(-1)[0] || sg.start const lastSeg = sg?.paths?.slice(-1)[0] || sg.start
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1` const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1`
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: _ast, node: _ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -645,7 +650,13 @@ export class SceneEntities {
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onClick: async (args) => { onClick: async (args) => {
if (!args) return if (!args) return
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args.mouseEvent.which !== 1) return if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args const { intersectionPoint } = args
let intersection2d = intersectionPoint?.twoD let intersection2d = intersectionPoint?.twoD
const profileStart = args.intersects const profileStart = args.intersects
@ -654,7 +665,7 @@ export class SceneEntities {
let modifiedAst let modifiedAst
if (profileStart) { if (profileStart) {
const lastSegment = sketch.value.slice(-1)[0] const lastSegment = sketch.paths.slice(-1)[0]
modifiedAst = addCallExpressionsToPipe({ modifiedAst = addCallExpressionsToPipe({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -686,7 +697,7 @@ export class SceneEntities {
}) })
if (trap(modifiedAst)) return Promise.reject(modifiedAst) if (trap(modifiedAst)) return Promise.reject(modifiedAst)
} else if (intersection2d) { } else if (intersection2d) {
const lastSegment = sketch.value.slice(-1)[0] const lastSegment = sketch.paths.slice(-1)[0]
const tmp = addNewSketchLn({ const tmp = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -817,7 +828,7 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
const sgPaths = sketch.value const sgPaths = sketch.paths
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch) this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
@ -826,6 +837,11 @@ export class SceneEntities {
) )
}, },
onClick: async (args) => { onClick: async (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
// Commit the rectangle to the full AST/code and return to sketch.idle // Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return if (!cornerPoint || args.mouseEvent.button !== 0) return
@ -868,7 +884,7 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return if (err(sketch)) return
const sgPaths = sketch.value const sgPaths = sketch.paths
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
// Update the starting segment of the THREEjs scene // Update the starting segment of the THREEjs scene
@ -985,7 +1001,7 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return if (err(sketch)) return
const sgPaths = sketch.value const sgPaths = sketch.paths
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch) this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
@ -994,6 +1010,11 @@ export class SceneEntities {
) )
}, },
onClick: async (args) => { onClick: async (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
// Commit the rectangle to the full AST/code and return to sketch.idle // Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return if (!cornerPoint || args.mouseEvent.button !== 0) return
@ -1105,7 +1126,7 @@ export class SceneEntities {
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
if (addingNewSegmentStatus === 'nothing') { if (addingNewSegmentStatus === 'nothing') {
const prevSegment = sketch.value[pipeIndex - 2] const prevSegment = sketch.paths[pipeIndex - 2]
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -1157,6 +1178,11 @@ export class SceneEntities {
}, },
onMove: () => {}, onMove: () => {},
onClick: (args) => { onClick: (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args?.mouseEvent.which !== 1) return if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) { if (!args || !args.selected) {
sceneInfra.modelingSend({ sceneInfra.modelingSend({
@ -1345,7 +1371,7 @@ export class SceneEntities {
} }
if (!sketch) return if (!sketch) return
const sgPaths = sketch.value const sgPaths = sketch.paths
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment( this.updateSegment(
@ -1393,7 +1419,7 @@ export class SceneEntities {
modifiedAst, modifiedAst,
segment.__geoMeta.sourceRange segment.__geoMeta.sourceRange
) )
const sgPaths = sketch.value const sgPaths = sketch.paths
const originalPathToNodeStr = JSON.stringify(segPathToNode) const originalPathToNodeStr = JSON.stringify(segPathToNode)
segPathToNode[1][0] = varDecIndex segPathToNode[1][0] = varDecIndex
const pathToNodeStr = JSON.stringify(segPathToNode) const pathToNodeStr = JSON.stringify(segPathToNode)
@ -1701,7 +1727,7 @@ function prepareTruncatedMemoryAndAst(
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return sg if (err(sg)) return sg
const lastSeg = sg?.value.slice(-1)[0] const lastSeg = sg?.paths.slice(-1)[0]
if (draftSegment) { if (draftSegment) {
// truncatedAst needs to setup with another segment at the end // truncatedAst needs to setup with another segment at the end
let newSegment let newSegment

View File

@ -58,7 +58,7 @@ import { err } from 'lib/trap'
interface CreateSegmentArgs { interface CreateSegmentArgs {
input: SegmentInputs input: SegmentInputs
prevSegment: Sketch['value'][number] prevSegment: Sketch['paths'][number]
id: string id: string
pathToNode: PathToNode pathToNode: PathToNode
isDraftSegment?: boolean isDraftSegment?: boolean
@ -72,7 +72,7 @@ interface CreateSegmentArgs {
interface UpdateSegmentArgs { interface UpdateSegmentArgs {
input: SegmentInputs input: SegmentInputs
prevSegment: Sketch['value'][number] prevSegment: Sketch['paths'][number]
group: Group group: Group
sceneInfra: SceneInfra sceneInfra: SceneInfra
scale?: number scale?: number

View File

@ -488,6 +488,12 @@ export const FileTreeInner = ({
// Refresh the file tree when there are changes. // Refresh the file tree when there are changes.
useFileSystemWatcher( useFileSystemWatcher(
async (eventType, path) => { async (eventType, path) => {
// Our other watcher races with this watcher on the current file changes,
// so we need to stop this one from reacting at all, otherwise Bad Things
// Happen™.
const isCurrentFile = loaderData.file?.path === path
const hasChanged = eventType === 'change'
if (isCurrentFile && hasChanged) return
fileSend({ type: 'Refresh' }) fileSend({ type: 'Refresh' })
}, },
[loaderData?.project?.path, fileContext.selectedDirectory.path].filter( [loaderData?.project?.path, fileContext.selectedDirectory.path].filter(

View File

@ -69,7 +69,7 @@ import { exportFromEngine } from 'lib/exportFromEngine'
import { Models } from '@kittycad/lib/dist/types/src' import { Models } from '@kittycad/lib/dist/types/src'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { EditorSelection, Transaction } from '@codemirror/state' import { EditorSelection, Transaction } from '@codemirror/state'
import { useNavigate, useSearchParams } from 'react-router-dom' import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls' import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
import { getVarNameModal } from 'hooks/useToolbarGuards' import { getVarNameModal } from 'hooks/useToolbarGuards'
import { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
@ -84,6 +84,7 @@ import {
import { submitAndAwaitTextToKcl } from 'lib/textToCad' import { submitAndAwaitTextToKcl } from 'lib/textToCad'
import { useFileContext } from 'hooks/useFileContext' import { useFileContext } from 'hooks/useFileContext'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
import { IndexLoaderData } from 'lib/types'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -116,6 +117,7 @@ export const ModelingMachineProvider = ({
} = useSettingsAuthContext() } = useSettingsAuthContext()
const navigate = useNavigate() const navigate = useNavigate()
const { context, send: fileMachineSend } = useFileContext() const { context, send: fileMachineSend } = useFileContext()
const { file } = useLoaderData() as IndexLoaderData
const token = auth?.context?.token const token = auth?.context?.token
const streamRef = useRef<HTMLDivElement>(null) const streamRef = useRef<HTMLDivElement>(null)
const persistedContext = useMemo(() => getPersistedContext(), []) const persistedContext = useMemo(() => getPersistedContext(), [])
@ -409,12 +411,15 @@ export const ModelingMachineProvider = ({
Make: ({ event }) => { Make: ({ event }) => {
if (event.type !== 'Make') return if (event.type !== 'Make') return
// Check if we already have an export intent. // Check if we already have an export intent.
if (engineCommandManager.exportIntent) { if (engineCommandManager.exportInfo) {
toast.error('Already exporting') toast.error('Already exporting')
return return
} }
// Set the export intent. // Set the export intent.
engineCommandManager.exportIntent = ExportIntent.Make engineCommandManager.exportInfo = {
intent: ExportIntent.Make,
name: file?.name || '',
}
// Set the current machine. // Set the current machine.
machineManager.currentMachine = event.data.machine machineManager.currentMachine = event.data.machine
@ -443,12 +448,16 @@ export const ModelingMachineProvider = ({
}, },
'Engine export': ({ event }) => { 'Engine export': ({ event }) => {
if (event.type !== 'Export') return if (event.type !== 'Export') return
if (engineCommandManager.exportIntent) { if (engineCommandManager.exportInfo) {
toast.error('Already exporting') toast.error('Already exporting')
return return
} }
// Set the export intent. // Set the export intent.
engineCommandManager.exportIntent = ExportIntent.Save engineCommandManager.exportInfo = {
intent: ExportIntent.Save,
// This never gets used its only for make.
name: '',
}
const format = { const format = {
...event.data, ...event.data,
@ -635,6 +644,7 @@ export const ModelingMachineProvider = ({
input.plane input.plane
) )
await kclManager.updateAst(modifiedAst, false) await kclManager.updateAst(modifiedAst, false)
sceneInfra.camControls.enableRotate = false
sceneInfra.camControls.syncDirection = 'clientToEngine' sceneInfra.camControls.syncDirection = 'clientToEngine'
await letEngineAnimateAndSyncCamAfter( await letEngineAnimateAndSyncCamAfter(

View File

@ -95,7 +95,7 @@ export const processMemory = (programMemory: ProgramMemory) => {
return rest return rest
}) })
} else if (!err(sg)) { } else if (!err(sg)) {
processedMemory[key] = sg.value.map(({ __geoMeta, ...rest }: Path) => { processedMemory[key] = sg.paths.map(({ __geoMeta, ...rest }: Path) => {
return rest return rest
}) })
} else if ((val.type as any) === 'Function') { } else if ((val.type as any) === 'Function') {

View File

@ -255,10 +255,14 @@ export const Stream = () => {
}, [mediaStream]) }, [mediaStream])
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => { const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
// If we've got no stream or connection, don't do anything
if (!isNetworkOkay) return if (!isNetworkOkay) return
if (!videoRef.current) return if (!videoRef.current) return
// If we're in sketch mode, don't send a engine-side select event
if (state.matches('Sketch')) return if (state.matches('Sketch')) return
if (state.matches({ idle: 'showPlanes' })) return if (state.matches({ idle: 'showPlanes' })) return
// If we're mousing up from a camera drag, don't send a select event
if (sceneInfra.camControls.wasDragging === true) return
if (btnName(e.nativeEvent).left) { if (btnName(e.nativeEvent).left) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises

View File

@ -32,7 +32,7 @@ const mySketch001 = startSketchOn('XY')
sourceRange: [46, 71], sourceRange: [46, 71],
}, },
}, },
value: [ paths: [
{ {
type: 'ToPoint', type: 'ToPoint',
tag: null, tag: null,
@ -96,7 +96,7 @@ const mySketch001 = startSketchOn('XY')
on: expect.any(Object), on: expect.any(Object),
start: expect.any(Object), start: expect.any(Object),
type: 'Sketch', type: 'Sketch',
value: [ paths: [
{ {
type: 'ToPoint', type: 'ToPoint',
from: [0, 0], from: [0, 0],
@ -202,7 +202,7 @@ const sk2 = startSketchOn('XY')
info: expect.any(Object), info: expect.any(Object),
}, },
}, },
value: [ paths: [
{ {
type: 'ToPoint', type: 'ToPoint',
from: [0, 0], from: [0, 0],
@ -294,7 +294,7 @@ const sk2 = startSketchOn('XY')
info: expect.any(Object), info: expect.any(Object),
}, },
}, },
value: [ paths: [
{ {
type: 'ToPoint', type: 'ToPoint',
from: [0, 0], from: [0, 0],

View File

@ -18,6 +18,7 @@ export default class CodeManager {
#updateState: (arg: string) => void = () => {} #updateState: (arg: string) => void = () => {}
private _currentFilePath: string | null = null private _currentFilePath: string | null = null
private _hotkeys: { [key: string]: () => void } = {} private _hotkeys: { [key: string]: () => void } = {}
private timeoutWriter: ReturnType<typeof setTimeout> | undefined = undefined
constructor() { constructor() {
if (isDesktop()) { if (isDesktop()) {
@ -115,7 +116,11 @@ export default class CodeManager {
async writeToFile() { async writeToFile() {
if (isDesktop()) { if (isDesktop()) {
setTimeout(() => { // Only write our buffer contents to file once per second. Any faster
// and file-system watchers which read, will receive empty data during
// writes.
clearTimeout(this.timeoutWriter)
this.timeoutWriter = setTimeout(() => {
// Wait one event loop to give a chance for params to be set // Wait one event loop to give a chance for params to be set
// Save the file to disk // Save the file to disk
this._currentFilePath && this._currentFilePath &&
@ -126,7 +131,7 @@ export default class CodeManager {
console.error('error saving file', err) console.error('error saving file', err)
toast.error('Error saving file, please check file permissions') toast.error('Error saving file, please check file permissions')
}) })
}) }, 1000)
} else { } else {
safeLSSetItem(PERSIST_CODE_KEY, this.code) safeLSSetItem(PERSIST_CODE_KEY, this.code)
} }

View File

@ -58,7 +58,7 @@ const newVar = myVar + 1`
` `
const mem = await exe(code) const mem = await exe(code)
// geo is three js buffer geometry and is very bloated to have in tests // geo is three js buffer geometry and is very bloated to have in tests
const minusGeo = mem.get('mySketch')?.value?.value const minusGeo = mem.get('mySketch')?.value?.paths
expect(minusGeo).toEqual([ expect(minusGeo).toEqual([
{ {
type: 'ToPoint', type: 'ToPoint',
@ -175,7 +175,7 @@ const newVar = myVar + 1`
info: expect.any(Object), info: expect.any(Object),
}, },
}, },
value: [ paths: [
{ {
type: 'ToPoint', type: 'ToPoint',
to: [1, 1], to: [1, 1],
@ -367,7 +367,7 @@ describe('testing math operators', () => {
const mem = await exe(code) const mem = await exe(code)
const sketch = sketchFromKclValue(mem.get('part001'), 'part001') const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
// result of `-legLen(5, min(3, 999))` should be -4 // result of `-legLen(5, min(3, 999))` should be -4
const yVal = (sketch as Sketch).value?.[0]?.to?.[1] const yVal = (sketch as Sketch).paths?.[0]?.to?.[1]
expect(yVal).toBe(-4) expect(yVal).toBe(-4)
}) })
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => { it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
@ -385,8 +385,8 @@ describe('testing math operators', () => {
const mem = await exe(code) const mem = await exe(code)
const sketch = sketchFromKclValue(mem.get('part001'), 'part001') const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0 // expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
expect((sketch as Sketch).value?.[1]?.from).toEqual([3, 4]) expect((sketch as Sketch).paths?.[1]?.from).toEqual([3, 4])
expect((sketch as Sketch).value?.[1]?.to).toEqual([6, 0]) expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0])
const removedUnaryExp = code.replace( const removedUnaryExp = code.replace(
`-legLen(segLen(seg01), myVar)`, `-legLen(segLen(seg01), myVar)`,
`legLen(segLen(seg01), myVar)` `legLen(segLen(seg01), myVar)`
@ -398,7 +398,7 @@ describe('testing math operators', () => {
) )
// without the minus sign, the y value should be 8 // without the minus sign, the y value should be 8
expect((removedUnaryExpMemSketch as Sketch).value?.[1]?.to).toEqual([6, 8]) expect((removedUnaryExpMemSketch as Sketch).paths?.[1]?.to).toEqual([6, 8])
}) })
it('with nested callExpression and binaryExpression', async () => { it('with nested callExpression and binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))' const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'

View File

@ -717,7 +717,7 @@ export function isLinesParallelAndConstrained(
constraintType === 'angle' || constraintLevel === 'full' constraintType === 'angle' || constraintLevel === 'full'
// get the previous segment // get the previous segment
const prevSegment = sg.value[secondaryIndex - 1] const prevSegment = sg.paths[secondaryIndex - 1]
const prevSourceRange = prevSegment.__geoMeta.sourceRange const prevSourceRange = prevSegment.__geoMeta.sourceRange
const isParallelAndConstrained = const isParallelAndConstrained =

View File

@ -50,6 +50,11 @@ export enum ExportIntent {
Make = 'make', Make = 'make',
} }
export interface ExportInfo {
intent: ExportIntent
name: string
}
type ClientMetrics = Models['ClientMetrics_type'] type ClientMetrics = Models['ClientMetrics_type']
interface WebRTCClientMetrics extends ClientMetrics { interface WebRTCClientMetrics extends ClientMetrics {
@ -1354,7 +1359,7 @@ export class EngineCommandManager extends EventTarget {
* export in progress. Otherwise it is an enum value of the intent. * export in progress. Otherwise it is an enum value of the intent.
* Another export cannot be started if one is already in progress. * Another export cannot be started if one is already in progress.
*/ */
private _exportIntent: ExportIntent | null = null private _exportInfo: ExportInfo | null = null
_commandLogCallBack: (command: CommandLog[]) => void = () => {} _commandLogCallBack: (command: CommandLog[]) => void = () => {}
subscriptions: { subscriptions: {
@ -1410,12 +1415,12 @@ export class EngineCommandManager extends EventTarget {
(() => {}) as any (() => {}) as any
kclManager: null | KclManager = null kclManager: null | KclManager = null
set exportIntent(intent: ExportIntent | null) { set exportInfo(info: ExportInfo | null) {
this._exportIntent = intent this._exportInfo = info
} }
get exportIntent() { get exportInfo() {
return this._exportIntent return this._exportInfo
} }
start({ start({
@ -1607,7 +1612,7 @@ export class EngineCommandManager extends EventTarget {
// because in all other cases we send JSON strings. But in the case of // because in all other cases we send JSON strings. But in the case of
// export we send a binary blob. // export we send a binary blob.
// Pass this to our export function. // Pass this to our export function.
if (this.exportIntent === null || this.pendingExport === undefined) { if (this.exportInfo === null || this.pendingExport === undefined) {
toast.error( toast.error(
'Export intent was not set, but export data was received' 'Export intent was not set, but export data was received'
) )
@ -1617,7 +1622,7 @@ export class EngineCommandManager extends EventTarget {
return return
} }
switch (this.exportIntent) { switch (this.exportInfo.intent) {
case ExportIntent.Save: { case ExportIntent.Save: {
exportSave(event.data, this.pendingExport.toastId).then(() => { exportSave(event.data, this.pendingExport.toastId).then(() => {
this.pendingExport?.resolve(null) this.pendingExport?.resolve(null)
@ -1625,21 +1630,22 @@ export class EngineCommandManager extends EventTarget {
break break
} }
case ExportIntent.Make: { case ExportIntent.Make: {
exportMake(event.data, this.pendingExport.toastId).then( exportMake(
(result) => { event.data,
this.exportInfo.name,
this.pendingExport.toastId
).then((result) => {
if (result) { if (result) {
this.pendingExport?.resolve(null) this.pendingExport?.resolve(null)
} else { } else {
this.pendingExport?.reject('Failed to make export') this.pendingExport?.reject('Failed to make export')
} }
}, }, this.pendingExport?.reject)
this.pendingExport?.reject
)
break break
} }
} }
// Set the export intent back to null. // Set the export intent back to null.
this.exportIntent = null this.exportInfo = null
return return
} }
@ -1953,15 +1959,15 @@ export class EngineCommandManager extends EventTarget {
return Promise.resolve(null) return Promise.resolve(null)
} else if (cmd.type === 'export') { } else if (cmd.type === 'export') {
const promise = new Promise<null>((resolve, reject) => { const promise = new Promise<null>((resolve, reject) => {
if (this.exportIntent === null) { if (this.exportInfo === null) {
if (this.exportIntent === null) { if (this.exportInfo === null) {
toast.error('Export intent was not set, but export is being sent') toast.error('Export intent was not set, but export is being sent')
console.error('Export intent was not set, but export is being sent') console.error('Export intent was not set, but export is being sent')
return return
} }
} }
const toastId = toast.loading( const toastId = toast.loading(
this.exportIntent === ExportIntent.Save this.exportInfo.intent === ExportIntent.Save
? EXPORT_TOAST_MESSAGES.START ? EXPORT_TOAST_MESSAGES.START
: MAKE_TOAST_MESSAGES.START : MAKE_TOAST_MESSAGES.START
) )
@ -1975,7 +1981,7 @@ export class EngineCommandManager extends EventTarget {
resolve(passThrough) resolve(passThrough)
}, },
reject: (reason: string) => { reject: (reason: string) => {
this.exportIntent = null this.exportInfo = null
reject(reason) reject(reason)
}, },
commandId: command.cmd_id, commandId: command.cmd_id,

View File

@ -64,7 +64,7 @@ const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"')
export type Coords2d = [number, number] export type Coords2d = [number, number]
export function getCoordsFromPaths(skGroup: Sketch, index = 0): Coords2d { export function getCoordsFromPaths(skGroup: Sketch, index = 0): Coords2d {
const currentPath = skGroup?.value?.[index] const currentPath = skGroup?.paths?.[index]
if (!currentPath && skGroup?.start) { if (!currentPath && skGroup?.start) {
return skGroup.start.to return skGroup.start.to
} else if (!currentPath) { } else if (!currentPath) {
@ -1704,7 +1704,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
varName varName
) )
if (err(sketch)) return sketch if (err(sketch)) return sketch
const intersectPath = sketch.value.find( const intersectPath = sketch.paths.find(
({ tag }: Path) => tag && tag.value === intersectTagName ({ tag }: Path) => tag && tag.value === intersectTagName
) )
let offset = 0 let offset = 0

View File

@ -18,7 +18,7 @@ export function getSketchSegmentFromPathToNode(
pathToNode: PathToNode pathToNode: PathToNode
): ):
| { | {
segment: Sketch['value'][number] segment: Sketch['paths'][number]
index: number index: number
} }
| Error { | Error {
@ -39,15 +39,15 @@ export function getSketchSegmentFromSourceRange(
[rangeStart, rangeEnd]: SourceRange [rangeStart, rangeEnd]: SourceRange
): ):
| { | {
segment: Sketch['value'][number] segment: Sketch['paths'][number]
index: number index: number
} }
| Error { | Error {
const lineIndex = sketch.value.findIndex( const lineIndex = sketch.paths.findIndex(
({ __geoMeta: { sourceRange } }: Path) => ({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
) )
const line = sketch.value[lineIndex] const line = sketch.paths[lineIndex]
if (line) { if (line) {
return { return {
segment: line, segment: line,

View File

@ -1732,7 +1732,7 @@ export function transformAstSketchLines({
if (err(_segment)) return _segment if (err(_segment)) return _segment
referencedSegment = _segment.segment referencedSegment = _segment.segment
} else { } else {
referencedSegment = sketch.value.find( referencedSegment = sketch.paths.find(
(path) => path.tag?.value === _referencedSegmentName (path) => path.tag?.value === _referencedSegmentName
) )
} }

View File

@ -92,6 +92,7 @@ export const MAKE_TOAST_MESSAGES = {
NO_MACHINE_API_IP: 'No machine api ip available', NO_MACHINE_API_IP: 'No machine api ip available',
NO_CURRENT_MACHINE: 'No current machine available', NO_CURRENT_MACHINE: 'No current machine available',
NO_MACHINE_ID: 'No machine id available', NO_MACHINE_ID: 'No machine id available',
NO_NAME: 'No name provided',
ERROR_STARTING_PRINT: 'Error while starting print', ERROR_STARTING_PRINT: 'Error while starting print',
SUCCESS: 'Started print successfully', SUCCESS: 'Started print successfully',
} }

View File

@ -8,8 +8,15 @@ import { MAKE_TOAST_MESSAGES } from './constants'
// Make files locally from an export call. // Make files locally from an export call.
export async function exportMake( export async function exportMake(
data: ArrayBuffer, data: ArrayBuffer,
name: string,
toastId: string toastId: string
): Promise<Response | null> { ): Promise<Response | null> {
if (name === '') {
console.error(MAKE_TOAST_MESSAGES.NO_NAME)
toast.error(MAKE_TOAST_MESSAGES.NO_NAME, { id: toastId })
return null
}
if (machineManager.machineCount() === 0) { if (machineManager.machineCount() === 0) {
console.error(MAKE_TOAST_MESSAGES.NO_MACHINES) console.error(MAKE_TOAST_MESSAGES.NO_MACHINES)
toast.error(MAKE_TOAST_MESSAGES.NO_MACHINES, { id: toastId }) toast.error(MAKE_TOAST_MESSAGES.NO_MACHINES, { id: toastId })
@ -39,7 +46,7 @@ export async function exportMake(
const params: components['schemas']['PrintParameters'] = { const params: components['schemas']['PrintParameters'] = {
machine_id: machineId, machine_id: machineId,
job_name: 'Exported Job', // TODO: make this the project name. job_name: name,
} }
try { try {
console.log('params', params) console.log('params', params)

View File

@ -138,6 +138,11 @@ export interface components {
FdmHardwareConfiguration: { FdmHardwareConfiguration: {
/** @description The filaments the printer has access to. */ /** @description The filaments the printer has access to. */
filaments: components['schemas']['Filament'][] filaments: components['schemas']['Filament'][]
/**
* Format: uint
* @description The currently loaded filament index.
*/
loaded_filament_idx?: number | null
/** /**
* Format: double * Format: double
* @description Diameter of the extrusion nozzle, in mm. * @description Diameter of the extrusion nozzle, in mm.
@ -191,6 +196,10 @@ export interface components {
/** @enum {string} */ /** @enum {string} */
type: 'composite' type: 'composite'
} }
| {
/** @enum {string} */
type: 'unknown'
}
/** @description The hardware configuration of a machine. */ /** @description The hardware configuration of a machine. */
HardwareConfiguration: HardwareConfiguration:
| { | {

View File

@ -23,6 +23,9 @@ import { codeManager, editorManager, kclManager } from 'lib/singletons'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { toSync } from 'lib/utils' import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { EngineConnectionStateType } from 'lang/std/engineConnection'
export const kbdClasses = export const kbdClasses =
'py-0.5 px-1 text-sm rounded bg-chalkboard-10 dark:bg-chalkboard-100 border border-chalkboard-50 border-b-2' 'py-0.5 px-1 text-sm rounded bg-chalkboard-10 dark:bg-chalkboard-100 border border-chalkboard-50 border-b-2'
@ -80,8 +83,20 @@ export const onboardingRoutes = [
] ]
export function useDemoCode() { export function useDemoCode() {
const { overallState, immediateState } = useNetworkContext()
useEffect(() => { useEffect(() => {
if (!editorManager.editorView || codeManager.code === bracket) return // Don't run if the editor isn't loaded or the code is already the bracket
if (!editorManager.editorView || codeManager.code === bracket) {
return
}
// Don't run if the network isn't healthy or the connection isn't established
if (
overallState !== NetworkHealthState.Ok ||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished
) {
return
}
setTimeout( setTimeout(
toSync(async () => { toSync(async () => {
codeManager.updateCodeStateEditor(bracket) codeManager.updateCodeStateEditor(bracket)
@ -89,7 +104,7 @@ export function useDemoCode() {
await codeManager.writeToFile() await codeManager.writeToFile()
}, reportRejection) }, reportRejection)
) )
}, [editorManager.editorView]) }, [editorManager.editorView, immediateState, overallState])
} }
export function useNextClick(newStatus: string) { export function useNextClick(newStatus: string) {

116
src/wasm-lib/Cargo.lock generated
View File

@ -121,9 +121,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.89" version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
dependencies = [ dependencies = [
"backtrace", "backtrace",
] ]
@ -176,7 +176,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -187,7 +187,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -204,7 +204,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -465,7 +465,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -656,7 +656,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -667,7 +667,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -722,7 +722,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
"synstructure", "synstructure",
] ]
@ -751,7 +751,7 @@ dependencies = [
"rustfmt-wrapper", "rustfmt-wrapper",
"serde", "serde",
"serde_tokenstream", "serde_tokenstream",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -762,7 +762,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -789,7 +789,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -827,7 +827,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -988,7 +988,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -1084,7 +1084,7 @@ dependencies = [
"inflections", "inflections",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -1612,7 +1612,7 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -1684,9 +1684,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.2.68" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3aedfcc1d8ea9995ec3eb78a6743c585c9380475c48701797f107489b696aa" checksum = "b135696d07a4fab928e5abace4dd05f4976eafab5d73e5747a85dc5a684b936c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1716,7 +1716,7 @@ dependencies = [
"kittycad-modeling-cmds-macros-impl", "kittycad-modeling-cmds-macros-impl",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -1727,7 +1727,7 @@ checksum = "6607507a8a0e4273b943179f0a3ef8e90712308d1d3095246040c29cfdbf985b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -2112,7 +2112,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -2126,7 +2126,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -2166,7 +2166,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -2224,7 +2224,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -2337,9 +2337,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.88" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -2391,7 +2391,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-macros-backend", "pyo3-macros-backend",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -2404,7 +2404,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-build-config", "pyo3-build-config",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -2925,7 +2925,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -2965,9 +2965,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.210" version = "1.0.213"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -2983,13 +2983,13 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.210" version = "1.0.213"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3000,14 +3000,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.128" version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [ dependencies = [
"indexmap 2.6.0", "indexmap 2.6.0",
"itoa", "itoa",
@ -3024,7 +3024,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3045,7 +3045,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3182,7 +3182,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"structmeta-derive", "structmeta-derive",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3193,7 +3193,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3215,7 +3215,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3237,9 +3237,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.79" version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3263,7 +3263,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3326,22 +3326,22 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.64" version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.64" version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3437,7 +3437,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3579,7 +3579,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3607,7 +3607,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3689,7 +3689,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
"termcolor", "termcolor",
] ]
@ -3865,7 +3865,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]
@ -3927,7 +3927,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3962,7 +3962,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4328,7 +4328,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn 2.0.85",
] ]
[[package]] [[package]]

View File

@ -72,7 +72,7 @@ members = [
[workspace.dependencies] [workspace.dependencies]
http = "1" http = "1"
kittycad = { version = "0.3.23", default-features = false, features = ["js", "requests"] } kittycad = { version = "0.3.23", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.68", features = ["websocket"] } kittycad-modeling-cmds = { version = "0.2.70", features = ["websocket"] }
[[test]] [[test]]
name = "executor" name = "executor"
@ -83,6 +83,6 @@ name = "modify"
path = "tests/modify/main.rs" path = "tests/modify/main.rs"
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone) # Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
#[patch."https://github.com/KittyCAD/modeling-api"] #[patch.crates-io]
#kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" } #kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" }
#kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" } #kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }

View File

@ -18,12 +18,12 @@ once_cell = "1.20.2"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
regex = "1.10" regex = "1.10"
serde = { version = "1.0.210", features = ["derive"] } serde = { version = "1.0.213", features = ["derive"] }
serde_tokenstream = "0.2" serde_tokenstream = "0.2"
syn = { version = "2.0.79", features = ["full"] } syn = { version = "2.0.85", features = ["full"] }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.89" anyhow = "1.0.91"
expectorate = "1.1.0" expectorate = "1.1.0"
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
rustfmt-wrapper = "0.2.1" rustfmt-wrapper = "0.2.1"

View File

@ -15,7 +15,7 @@ databake = "0.1.8"
kcl-lib = { path = "../kcl" } kcl-lib = { path = "../kcl" }
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
syn = { version = "2.0.79", features = ["full"] } syn = { version = "2.0.85", features = ["full"] }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"

View File

@ -12,7 +12,7 @@ fn basic() {
let expected = Program { let expected = Program {
start: 0, start: 0,
end: 11, end: 11,
body: vec![BodyItem::VariableDeclaration(VariableDeclaration { body: vec![BodyItem::VariableDeclaration(Box::new(VariableDeclaration {
start: 0, start: 0,
end: 11, end: 11,
declarations: vec![VariableDeclarator { declarations: vec![VariableDeclarator {
@ -36,7 +36,7 @@ fn basic() {
visibility: ItemVisibility::Default, visibility: ItemVisibility::Default,
kind: VariableKind::Const, kind: VariableKind::Const,
digest: None, digest: None,
})], }))],
non_code_meta: NonCodeMeta::default(), non_code_meta: NonCodeMeta::default(),
digest: None, digest: None,
}; };

View File

@ -6,10 +6,10 @@ edition = "2021"
license = "MIT" license = "MIT"
[dependencies] [dependencies]
anyhow = "1.0.89" anyhow = "1.0.91"
hyper = { version = "0.14.29", features = ["http1", "server", "tcp"] } hyper = { version = "0.14.29", features = ["http1", "server", "tcp"] }
kcl-lib = { version = "0.2", path = "../kcl" } kcl-lib = { version = "0.2", path = "../kcl" }
pico-args = "0.5.0" pico-args = "0.5.0"
serde = { version = "1.0.210", features = ["derive"] } serde = { version = "1.0.213", features = ["derive"] }
serde_json = "1.0.128" serde_json = "1.0.128"
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }

View File

@ -11,7 +11,7 @@ keywords = ["kcl", "KittyCAD", "CAD"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = { version = "1.0.89", features = ["backtrace"] } anyhow = { version = "1.0.91", features = ["backtrace"] }
async-recursion = "1.1.1" async-recursion = "1.1.1"
async-trait = "0.1.83" async-trait = "0.1.83"
base64 = "0.22.1" base64 = "0.22.1"
@ -38,11 +38,11 @@ pyo3 = { version = "0.22.5", optional = true }
reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls"] } reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls"] }
ropey = "1.6.1" ropey = "1.6.1"
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1", "preserve_order"] } schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1", "preserve_order"] }
serde = { version = "1.0.210", features = ["derive"] } serde = { version = "1.0.213", features = ["derive"] }
serde_json = "1.0.128" serde_json = "1.0.128"
sha2 = "0.10.8" sha2 = "0.10.8"
tabled = { version = "0.15.0", optional = true } tabled = { version = "0.15.0", optional = true }
thiserror = "1.0.64" thiserror = "1.0.65"
toml = "0.8.19" toml = "0.8.19"
ts-rs = { version = "10.0.0", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] } ts-rs = { version = "10.0.0", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
url = { version = "2.5.2", features = ["serde"] } url = { version = "2.5.2", features = ["serde"] }

View File

@ -454,7 +454,7 @@ pub(crate) use impl_value_meta;
pub enum BodyItem { pub enum BodyItem {
ImportStatement(Box<ImportStatement>), ImportStatement(Box<ImportStatement>),
ExpressionStatement(ExpressionStatement), ExpressionStatement(ExpressionStatement),
VariableDeclaration(VariableDeclaration), VariableDeclaration(Box<VariableDeclaration>),
ReturnStatement(ReturnStatement), ReturnStatement(ReturnStatement),
} }
@ -2719,7 +2719,7 @@ pub struct FunctionExpression {
impl_value_meta!(FunctionExpression); impl_value_meta!(FunctionExpression);
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct RequiredParamAfterOptionalParam(pub Parameter); pub struct RequiredParamAfterOptionalParam(pub Box<Parameter>);
impl std::fmt::Display for RequiredParamAfterOptionalParam { impl std::fmt::Display for RequiredParamAfterOptionalParam {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -2751,7 +2751,7 @@ impl FunctionExpression {
if param.optional { if param.optional {
found_optional = true; found_optional = true;
} else if found_optional { } else if found_optional {
return Err(RequiredParamAfterOptionalParam(param.clone())); return Err(RequiredParamAfterOptionalParam(Box::new(param.clone())));
} }
} }
let boundary = self.params.partition_point(|param| !param.optional); let boundary = self.params.partition_point(|param| !param.optional);

View File

@ -551,13 +551,13 @@ impl ArrayRangeExpression {
.execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression) .execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression)
.await? .await?
.get_json_value()?; .get_json_value()?;
let start = parse_json_number_as_u64(&start, (&*self.start_element).into())?; let start = parse_json_number_as_i64(&start, (&*self.start_element).into())?;
let metadata = Metadata::from(&*self.end_element); let metadata = Metadata::from(&*self.end_element);
let end = ctx let end = ctx
.execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression) .execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression)
.await? .await?
.get_json_value()?; .get_json_value()?;
let end = parse_json_number_as_u64(&end, (&*self.end_element).into())?; let end = parse_json_number_as_i64(&end, (&*self.end_element).into())?;
if end < start { if end < start {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
@ -603,9 +603,9 @@ impl ObjectExpression {
} }
} }
pub fn parse_json_number_as_u64(j: &serde_json::Value, source_range: SourceRange) -> Result<u64, KclError> { fn parse_json_number_as_i64(j: &serde_json::Value, source_range: SourceRange) -> Result<i64, KclError> {
if let serde_json::Value::Number(n) = &j { if let serde_json::Value::Number(n) = &j {
n.as_u64().ok_or_else(|| { n.as_i64().ok_or_else(|| {
KclError::Syntax(KclErrorDetails { KclError::Syntax(KclErrorDetails {
source_ranges: vec![source_range], source_ranges: vec![source_range],
message: format!("Invalid integer: {}", j), message: format!("Invalid integer: {}", j),

View File

@ -1137,10 +1137,10 @@ pub struct TagEngineInfo {
#[ts(export)] #[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
pub struct Sketch { pub struct Sketch {
/// The id of the sketch (this will change when the engine's reference to it changes. /// The id of the sketch (this will change when the engine's reference to it changes).
pub id: uuid::Uuid, pub id: uuid::Uuid,
/// The paths in the sketch. /// The paths in the sketch.
pub value: Vec<Path>, pub paths: Vec<Path>,
/// What the sketch is on (can be a plane or a face). /// What the sketch is on (can be a plane or a face).
pub on: SketchSurface, pub on: SketchSurface,
/// The starting path. /// The starting path.
@ -1215,7 +1215,7 @@ impl Sketch {
/// Get the path most recently sketched. /// Get the path most recently sketched.
pub(crate) fn latest_path(&self) -> Option<&Path> { pub(crate) fn latest_path(&self) -> Option<&Path> {
self.value.last() self.paths.last()
} }
/// The "pen" is an imaginary pen drawing the path. /// The "pen" is an imaginary pen drawing the path.

View File

@ -1342,7 +1342,7 @@ fn declaration_keyword(i: TokenSlice) -> PResult<(VariableKind, Token)> {
} }
/// Parse a variable/constant declaration. /// Parse a variable/constant declaration.
fn declaration(i: TokenSlice) -> PResult<VariableDeclaration> { fn declaration(i: TokenSlice) -> PResult<Box<VariableDeclaration>> {
let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace)) let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
.parse_next(i)? .parse_next(i)?
.map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1))); .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
@ -1404,7 +1404,7 @@ fn declaration(i: TokenSlice) -> PResult<VariableDeclaration> {
.map_err(|e| e.cut())?; .map_err(|e| e.cut())?;
let end = val.end(); let end = val.end();
Ok(VariableDeclaration { Ok(Box::new(VariableDeclaration {
start, start,
end, end,
declarations: vec![VariableDeclarator { declarations: vec![VariableDeclarator {
@ -1417,7 +1417,7 @@ fn declaration(i: TokenSlice) -> PResult<VariableDeclaration> {
visibility, visibility,
kind, kind,
digest: None, digest: None,
}) }))
} }
impl TryFrom<Token> for Identifier { impl TryFrom<Token> for Identifier {

View File

@ -141,14 +141,14 @@ pub(crate) async fn do_post_extrude(
) )
.await?; .await?;
if sketch.value.is_empty() { if sketch.paths.is_empty() {
return Err(KclError::Type(KclErrorDetails { return Err(KclError::Type(KclErrorDetails {
message: "Expected a non-empty sketch".to_string(), message: "Expected a non-empty sketch".to_string(),
source_ranges: vec![args.source_range], source_ranges: vec![args.source_range],
})); }));
} }
let edge_id = sketch.value.iter().find_map(|segment| match segment { let edge_id = sketch.paths.iter().find_map(|segment| match segment {
Path::ToPoint { base } | Path::Circle { base, .. } => Some(base.geo_meta.id), Path::ToPoint { base } | Path::Circle { base, .. } => Some(base.geo_meta.id),
_ => None, _ => None,
}); });
@ -229,7 +229,7 @@ pub(crate) async fn do_post_extrude(
} = analyze_faces(exec_state, &args, face_infos); } = analyze_faces(exec_state, &args, face_infos);
// Iterate over the sketch.value array and add face_id to GeoMeta // Iterate over the sketch.value array and add face_id to GeoMeta
let new_value = sketch let new_value = sketch
.value .paths
.iter() .iter()
.flat_map(|path| { .flat_map(|path| {
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) { if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {

View File

@ -109,7 +109,7 @@ pub async fn last_segment_x(_exec_state: &mut ExecState, args: Args) -> Result<K
}] }]
fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<f64, KclError> { fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<f64, KclError> {
let last_line = sketch let last_line = sketch
.value .paths
.last() .last()
.ok_or_else(|| { .ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
@ -149,7 +149,7 @@ pub async fn last_segment_y(_exec_state: &mut ExecState, args: Args) -> Result<K
}] }]
fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<f64, KclError> { fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<f64, KclError> {
let last_line = sketch let last_line = sketch
.value .paths
.last() .last()
.ok_or_else(|| { .ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
@ -289,7 +289,7 @@ fn inner_angle_to_match_length_x(
let length = ((path.from[1] - path.to[1]).powi(2) + (path.from[0] - path.to[0]).powi(2)).sqrt(); let length = ((path.from[1] - path.to[1]).powi(2) + (path.from[0] - path.to[0]).powi(2)).sqrt();
let last_line = sketch let last_line = sketch
.value .paths
.last() .last()
.ok_or_else(|| { .ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
@ -353,7 +353,7 @@ fn inner_angle_to_match_length_y(
let length = ((path.from[1] - path.to[1]).powi(2) + (path.from[0] - path.to[0]).powi(2)).sqrt(); let length = ((path.from[1] - path.to[1]).powi(2) + (path.from[0] - path.to[0]).powi(2)).sqrt();
let last_line = sketch let last_line = sketch
.value .paths
.last() .last()
.ok_or_else(|| { .ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {

View File

@ -134,7 +134,7 @@ async fn inner_circle(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id })) args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id }))
.await?; .await?;

View File

@ -154,7 +154,7 @@ async fn inner_line_to(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -323,7 +323,7 @@ async fn inner_line(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -506,7 +506,7 @@ async fn inner_angled_line(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1237,7 +1237,7 @@ pub(crate) async fn inner_start_profile_at(
id: path_id, id: path_id,
original_id: path_id, original_id: path_id,
on: sketch_surface.clone(), on: sketch_surface.clone(),
value: vec![], paths: vec![],
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
tags: if let Some(tag) = &tag { tags: if let Some(tag) = &tag {
let mut tag_identifier: TagIdentifier = tag.into(); let mut tag_identifier: TagIdentifier = tag.into();
@ -1411,7 +1411,7 @@ pub(crate) async fn inner_close(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1545,7 +1545,7 @@ pub(crate) async fn inner_arc(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1677,7 +1677,7 @@ async fn inner_tangential_arc(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1773,7 +1773,7 @@ async fn inner_tangential_arc_to(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1858,7 +1858,7 @@ async fn inner_tangential_arc_to_relative(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1951,7 +1951,7 @@ async fn inner_bezier_curve(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.value.push(current_path); new_sketch.paths.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }

View File

@ -290,7 +290,7 @@ where
walk_value(&xs.expression, f) walk_value(&xs.expression, f)
} }
BodyItem::VariableDeclaration(vd) => { BodyItem::VariableDeclaration(vd) => {
if !f.walk(vd.into())? { if !f.walk(vd.as_ref().into())? {
return Ok(false); return Ok(false);
} }
for dec in &vd.declarations { for dec in &vd.declarations {

View File

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "1.81.0" channel = "1.82.0"
components = ["clippy", "rustfmt"] components = ["clippy", "rustfmt"]

View File

@ -1,5 +1,5 @@
fn cube = (pos, scale) => { fn square = (pos, scale) => {
const sg = startSketchOn('XY') sg = startSketchOn('XY')
|> startProfileAt(pos, %) |> startProfileAt(pos, %)
|> line([0, scale], %) |> line([0, scale], %)
|> line([scale, 0], %) |> line([scale, 0], %)
@ -9,9 +9,9 @@ fn cube = (pos, scale) => {
return sg return sg
} }
const b1 = cube([0,0], 10) sq = square([0,0], 10)
const b2 = cube([3,3], 4) cb = square([3,3], 4)
|> extrude(10, %) |> extrude(10, %)
const pt1 = b1.value[0] pt1 = sq.paths[0]
const pt2 = b2.value[0] pt2 = cb.value[0]

View File

@ -1,3 +1,2 @@
xs = [-5..5] xs = [int(-5)..5]
assertEqual(xs[0], -5, 0.001, "first element is -5") assertEqual(xs[0], -5, 0.001, "first element is -5")
assert(false)

View File

@ -101,7 +101,7 @@ gen_test!(property_of_object);
gen_test!(index_of_array); gen_test!(index_of_array);
gen_test!(comparisons); gen_test!(comparisons);
gen_test!(array_range_expr); gen_test!(array_range_expr);
gen_test_fail!(array_range_negative_expr, "syntax: Invalid integer: -5.0"); gen_test!(array_range_negative_expr);
gen_test_fail!( gen_test_fail!(
invalid_index_str, invalid_index_str,
"semantic: Only integers >= 0 can be used as the index of an array, but you're using a string" "semantic: Only integers >= 0 can be used as the index of an array, but you're using a string"

View File

@ -2597,10 +2597,10 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433"
integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==
"@types/node@*", "@types/node@^22.5.0": "@types/node@*", "@types/node@^22.7.8":
version "22.5.0" version "22.7.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.0.tgz#10f01fe9465166b4cab72e75f60d8b99d019f958" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.8.tgz#a1dbf0dc5f71bdd2642fc89caef65d58747ce825"
integrity sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg== integrity sha512-a922jJy31vqR5sk+kAdIENJjHblqcZ4RmERviFsER4WJcEONqxKcjNOlk0q7OUfrF5sddT+vng070cdfMlrPLg==
dependencies: dependencies:
undici-types "~6.19.2" undici-types "~6.19.2"
@ -8785,7 +8785,16 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: "string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -8879,7 +8888,14 @@ string_decoder@~1.1.1:
dependencies: dependencies:
safe-buffer "~5.1.0" safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: "strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -9753,7 +9769,16 @@ word-wrap@^1.2.3, word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==