Compare commits

..

82 Commits

Author SHA1 Message Date
9407162543 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation-backup 2024-12-09 14:04:18 -05:00
067b83b468 Manual resolution of snapshot conflicts 2024-12-09 13:56:33 -05:00
c5b30341eb Add unit tests for doesSceneHaveExtrudedSketch 2024-12-09 12:26:21 -05:00
3e6441b563 Fix test annotations 2024-12-09 11:36:46 -05:00
acafcf2d4d Apply suggestions from Frank's review
Co-authored-by: Frank Noirot <frank@zoo.dev>
2024-12-09 11:33:41 -05:00
b9f31d94d5 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-05 18:43:44 -05:00
9e03b58ae5 Cap and wall pw test 2024-12-05 18:21:20 -05:00
c591f73c70 Working multi-face shell across types 2024-12-05 17:56:18 -05:00
9330aaba13 Trigger CI 2024-12-05 17:27:16 -05:00
5a14f0189e Look at this (photo)Graph *in the voice of Nickelback* 2024-12-05 22:14:20 +00:00
9af001f22e Lint 2024-12-05 17:04:02 -05:00
435d1ea52e WIP circular dep 2024-12-05 16:53:57 -05:00
54847139f2 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-05 15:00:34 -05:00
9369a17ea7 WIP mutliple faces 2024-12-05 14:59:44 -05:00
9ee2e7c3b0 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-05 19:42:39 +00:00
28ae261e5f Lint fix 2024-12-05 14:38:23 -05:00
e4b0de0ead Add selection guard and clean up 2024-12-05 14:06:09 -05:00
12859598a3 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-05 13:02:35 -05:00
e9a334f433 Fix lint 2024-12-05 13:02:22 -05:00
e470a7b4af Add shell wall test 2024-12-05 12:33:46 -05:00
602c39f63c Add pw tests for cap shell 2024-12-05 12:03:45 -05:00
4994aa6f61 Handle walls 2024-12-05 11:38:03 -05:00
4aa07b81db Add extrude lookup for more generic shell 2024-12-05 10:26:58 -05:00
89ef4b3243 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-05 14:55:04 +00:00
001c9a8219 Update from main 2024-12-05 09:49:14 -05:00
4dde3f60e0 WIP: first time working shell mod 2024-12-04 18:41:45 -05:00
f407c53032 WIP: closer 2024-12-04 17:53:04 -05:00
9ba584487a WIP: more additions 2024-12-04 17:01:11 -05:00
96b66d6bca Merge branch 'pierremtb/issue4470-loft-ui' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-04 16:44:40 -05:00
8d66f3ffad Rollback pw values to pre cam change 2024-12-04 16:43:29 -05:00
f4c54cbbe4 WIP: initial shell code addition 2024-12-04 16:38:44 -05:00
5156b847f3 Trigger CI 2024-12-04 15:56:48 -05:00
ded9f2c56b A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 20:54:59 +00:00
5db5f79f9a Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-12-04 15:46:18 -05:00
6a883f4a8d A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 20:43:39 +00:00
07b91f0fb1 Trigger CI 2024-12-04 15:39:26 -05:00
1b2e213afe A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 20:37:28 +00:00
f48a23c35e A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 20:36:20 +00:00
7e1d102496 Revert snapshots 2024-12-04 15:32:14 -05:00
94cb2535c0 Fix typo 2024-12-04 15:21:03 -05:00
9e08ec9096 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 20:00:36 +00:00
17bd8ec32a A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 19:58:28 +00:00
d0f12e85e5 Trigger CI 2024-12-04 14:54:05 -05:00
94d185944e A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 19:52:22 +00:00
c38b2270c3 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 19:51:34 +00:00
967ad66c98 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 19:47:04 +00:00
afeca9ca39 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 19:46:25 +00:00
61242282f0 Trigger CI 2024-12-04 14:42:00 -05:00
0065df13ce A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 16:55:39 +00:00
01c8d45c13 Remove comments 2024-12-04 11:44:49 -05:00
8b25527f21 Move error logic out of loftSketches, fix pw tests 2024-12-04 11:39:35 -05:00
2abd980de9 Move to fromPromise-based Actor 2024-12-04 11:02:51 -05:00
f783deb706 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 15:25:38 +00:00
f4dd295ca1 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 15:17:19 +00:00
ceaa85fe3f A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 15:16:16 +00:00
3991bd9173 Trigger CI 2024-12-04 10:11:47 -05:00
b8f9da36c0 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 10:54:14 +00:00
283315b5d2 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 10:44:01 +00:00
e204dfe564 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 10:43:15 +00:00
208a36196b Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-12-04 05:38:43 -05:00
660a349588 Add pw test for preselected sketches 2024-12-03 15:17:21 -05:00
56c37da317 Clean up loftSketches function 2024-12-03 15:02:56 -05:00
a46734b76d Add test for doesSceneHaveSweepableSketch with count = 2 2024-12-03 14:39:06 -05:00
4347e0cf84 Clean up and working pw test 2024-12-03 13:49:26 -05:00
df3e541cdf A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-03 17:51:57 +00:00
b1cec443b9 Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-12-03 12:48:32 -05:00
967f49055d Lint 2024-12-02 14:30:43 -05:00
fe977524b5 First point-click loft test (not working locally, loft gets inserted at the wrong place) 2024-12-02 14:19:32 -05:00
d6f271fb0f Enable multiple selections after the button click 2024-12-01 07:00:13 -05:00
e851b2bcc4 Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-11-29 20:01:32 -05:00
be569c91de Clean up 2024-11-29 19:51:56 -05:00
5080e304b9 Appends the loft line after the 'last' sketch in the code 2024-11-29 18:30:15 -05:00
f4e75b7b4f More checks 2024-11-29 13:38:52 -05:00
31cbc90f56 WIP selections 2024-11-29 12:37:53 -05:00
a7d3552472 WIP selections 2024-11-29 11:17:31 -05:00
e984b20664 First pass at handling more than 2 sketches 2024-11-29 10:09:10 -05:00
0c2cd24bda Working loft for two sketches in the right hardcoded order 2024-11-28 20:28:37 -05:00
9b2de237b8 Add selection guard 2024-11-28 16:45:41 -05:00
c79c02f18e A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-11-28 20:25:44 +00:00
4851aa2d71 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-11-28 20:17:17 +00:00
76fafa6fd0 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-11-28 20:11:35 +00:00
647ca11e08 WIP: experimenting with Loft UI
Relates to #4470
2024-11-27 15:55:01 -05:00
225 changed files with 4472 additions and 19742 deletions

View File

@ -165,6 +165,7 @@ jobs:
- name: Build the app (release) - name: Build the app (release)
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
env: env:
PUBLISH_FOR_PULL_REQUEST: true
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
@ -172,6 +173,7 @@ jobs:
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: yarn electron-builder --config --publish always run: yarn electron-builder --config --publish always
@ -227,6 +229,7 @@ jobs:
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: yarn electron-builder --config --publish always run: yarn electron-builder --config --publish always

View File

@ -2,8 +2,28 @@ on:
push: push:
branches: branches:
- main - main
paths:
- 'src/wasm-lib/**.rs'
- 'src/wasm-lib/**.hbs'
- 'src/wasm-lib/**.gen'
- 'src/wasm-lib/**.snap'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- 'src/wasm-lib/**.kcl'
- .github/workflows/cargo-test.yml
pull_request: pull_request:
paths:
- 'src/wasm-lib/**.rs'
- 'src/wasm-lib/**.hbs'
- 'src/wasm-lib/**.gen'
- 'src/wasm-lib/**.snap'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- 'src/wasm-lib/**.kcl'
- .github/workflows/cargo-test.yml
workflow_dispatch: workflow_dispatch:
permissions: read-all permissions: read-all
concurrency: concurrency:
@ -51,7 +71,7 @@ jobs:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000 RUST_MIN_STACK: 10485760000
- name: Upload to codecov.io - name: Upload to codecov.io
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v4
with: with:
token: ${{secrets.CODECOV_TOKEN}} token: ${{secrets.CODECOV_TOKEN}}
fail_ci_if_error: true fail_ci_if_error: true

View File

@ -22,5 +22,3 @@ once fixed in engine will just start working here with no language changes.
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple - **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
chamfer cases work currently. chamfer cases work currently.
- **Appearance**: Changing the appearance on a loft does not work.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,6 @@ layout: manual
* [`angledLineThatIntersects`](kcl/angledLineThatIntersects) * [`angledLineThatIntersects`](kcl/angledLineThatIntersects)
* [`angledLineToX`](kcl/angledLineToX) * [`angledLineToX`](kcl/angledLineToX)
* [`angledLineToY`](kcl/angledLineToY) * [`angledLineToY`](kcl/angledLineToY)
* [`appearance`](kcl/appearance)
* [`arc`](kcl/arc) * [`arc`](kcl/arc)
* [`arcTo`](kcl/arcTo) * [`arcTo`](kcl/arcTo)
* [`asin`](kcl/asin) * [`asin`](kcl/asin)
@ -30,7 +29,6 @@ layout: manual
* [`assertLessThan`](kcl/assertLessThan) * [`assertLessThan`](kcl/assertLessThan)
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq) * [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
* [`atan`](kcl/atan) * [`atan`](kcl/atan)
* [`atan2`](kcl/atan2)
* [`bezierCurve`](kcl/bezierCurve) * [`bezierCurve`](kcl/bezierCurve)
* [`ceil`](kcl/ceil) * [`ceil`](kcl/ceil)
* [`chamfer`](kcl/chamfer) * [`chamfer`](kcl/chamfer)
@ -103,7 +101,6 @@ layout: manual
* [`startProfileAt`](kcl/startProfileAt) * [`startProfileAt`](kcl/startProfileAt)
* [`startSketchAt`](kcl/startSketchAt) * [`startSketchAt`](kcl/startSketchAt)
* [`startSketchOn`](kcl/startSketchOn) * [`startSketchOn`](kcl/startSketchOn)
* [`sweep`](kcl/sweep)
* [`tan`](kcl/tan) * [`tan`](kcl/tan)
* [`tangentToEnd`](kcl/tangentToEnd) * [`tangentToEnd`](kcl/tangentToEnd)
* [`tangentialArc`](kcl/tangentialArc) * [`tangentialArc`](kcl/tangentialArc)

File diff suppressed because one or more lines are too long

View File

@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
```js ```js
r = 10 // radius r = 10 // radius
// Call `map`, using an anonymous function instead of a named one. // Call `map`, using an anonymous function instead of a named one.
circles = map([1..3], fn(id) { circles = map([1..3], (id) {
return startSketchOn("XY") return startSketchOn("XY")
|> circle({ center = [id * 2 * r, 0], radius = r }, %) |> circle({ center = [id * 2 * r, 0], radius = r }, %)
}) })

View File

@ -43,7 +43,7 @@ fn sum(arr) {
/* The above is basically like this pseudo-code: /* The above is basically like this pseudo-code:
fn sum(arr): fn sum(arr):
sumSoFar = 0 let sumSoFar = 0
for i in arr: for i in arr:
sumSoFar = add(sumSoFar, i) sumSoFar = add(sumSoFar, i)
return sumSoFar */ return sumSoFar */
@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
// an anonymous `add` function as its parameter, instead of declaring a // an anonymous `add` function as its parameter, instead of declaring a
// named function outside. // named function outside.
arr = [1, 2, 3] arr = [1, 2, 3]
sum = reduce(arr, 0, fn(i, result_so_far) { sum = reduce(arr, 0, (i, result_so_far) {
return i + result_so_far return i + result_so_far
}) })
@ -84,7 +84,7 @@ fn decagon(radius) {
// Use a `reduce` to draw the remaining decagon sides. // Use a `reduce` to draw the remaining decagon sides.
// For each number in the array 1..10, run the given function, // For each number in the array 1..10, run the given function,
// which takes a partially-sketched decagon and adds one more edge to it. // which takes a partially-sketched decagon and adds one more edge to it.
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) { fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {
// Draw one edge of the decagon. // Draw one edge of the decagon.
x = cos(stepAngle * i) * radius x = cos(stepAngle * i) * radius
y = sin(stepAngle * i) * radius y = sin(stepAngle * i) * radius
@ -96,14 +96,14 @@ fn decagon(radius) {
/* The `decagon` above is basically like this pseudo-code: /* The `decagon` above is basically like this pseudo-code:
fn decagon(radius): fn decagon(radius):
stepAngle = (1/10) * tau() let stepAngle = (1/10) * tau()
startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)]) let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
// Here's the reduce part. // Here's the reduce part.
partialDecagon = startOfDecagonSketch let partialDecagon = startOfDecagonSketch
for i in [1..10]: for i in [1..10]:
x = cos(stepAngle * i) * radius let x = cos(stepAngle * i) * radius
y = sin(stepAngle * i) * radius let y = sin(stepAngle * i) * radius
partialDecagon = lineTo([x, y], partialDecagon) partialDecagon = lineTo([x, y], partialDecagon)
fullDecagon = partialDecagon // it's now full fullDecagon = partialDecagon // it's now full
return fullDecagon */ return fullDecagon */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,23 +0,0 @@
---
title: "AppearanceData"
excerpt: "Data for appearance."
layout: manual
---
Data for appearance.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `color` |`string`| Color of the new material, a hex string like "#ff0000". | No |
| `metalness` |`number` (**maximum:** 100.0)| Metalness of the new material, a percentage like 95.7. | No |
| `roughness` |`number` (**maximum:** 100.0)| Roughness of the new material, a percentage like 95.7. | No |

View File

@ -12,10 +12,5 @@ KCL value for an optional parameter which was not given an argument. (remember,
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |

View File

@ -1,23 +0,0 @@
---
title: "SweepData"
excerpt: "Data for a sweep."
layout: manual
---
Data for a sweep.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No |
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
| `tolerance` |`number`| Tolerance for the sweep operation. | No |

View File

@ -94,51 +94,6 @@ test.describe('Editor tests', () => {
|> close(%)`) |> close(%)`)
}) })
test('ensure we use the cache, and do not re-execute', async ({ page }) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1000, height: 500 })
await u.waitForAuthSkipAppStart()
await u.codeLocator.click()
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)`)
// Ensure we execute the first time.
await u.openDebugPanel()
await expect(
page.locator('[data-receive-command-type="scene_clear_all"]')
).toHaveCount(2)
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(2)
// Add whitespace to the end of the code.
await u.codeLocator.click()
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('Home')
await page.keyboard.type(' ')
await page.keyboard.press('Enter')
await page.keyboard.type(' ')
// Ensure we don't execute the second time.
await u.openDebugPanel()
// Make sure we didn't clear the scene.
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(3)
await expect(
page.locator('[data-receive-command-type="scene_clear_all"]')
).toHaveCount(2)
})
test('if you click the format button it formats your code and executes so lints are still there', async ({ test('if you click the format button it formats your code and executes so lints are still there', async ({
page, page,
}) => { }) => {

View File

@ -214,7 +214,23 @@ export class SceneFixture {
coords: { x: number; y: number }, coords: { x: number; y: number },
diff: number diff: number
) => { ) => {
await expectPixelColor(this.page, colour, coords, diff) let finalValue = colour
await expect
.poll(async () => {
const pixel = (await getPixelRGBs(this.page)(coords, 1))[0]
if (!pixel) return null
finalValue = pixel
return pixel.every(
(channel, index) => Math.abs(channel - colour[index]) < diff
)
})
.toBeTruthy()
.catch((cause) => {
throw new Error(
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
{ cause }
)
})
} }
get gizmo() { get gizmo() {
@ -230,28 +246,3 @@ export class SceneFixture {
await buttonToTest.click() await buttonToTest.click()
} }
} }
export async function expectPixelColor(
page: Page,
colour: [number, number, number],
coords: { x: number; y: number },
diff: number
) {
let finalValue = colour
await expect
.poll(async () => {
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
if (!pixel) return null
finalValue = pixel
return pixel.every(
(channel, index) => Math.abs(channel - colour[index]) < diff
)
})
.toBeTruthy()
.catch((cause) => {
throw new Error(
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
{ cause }
)
})
}

View File

@ -19,7 +19,6 @@ import {
TEST_SETTINGS_ONBOARDING_USER_MENU, TEST_SETTINGS_ONBOARDING_USER_MENU,
} from './storageStates' } from './storageStates'
import * as TOML from '@iarna/toml' import * as TOML from '@iarna/toml'
import { expectPixelColor } from './fixtures/sceneFixture'
test.beforeEach(async ({ context, page }, testInfo) => { test.beforeEach(async ({ context, page }, testInfo) => {
if (testInfo.tags.includes('@electron')) { if (testInfo.tags.includes('@electron')) {
@ -46,7 +45,7 @@ test.describe('Onboarding tests', () => {
{ settingsKey: TEST_SETTINGS_KEY } { settingsKey: TEST_SETTINGS_KEY }
) )
await page.setViewportSize({ width: 1200, height: 1000 }) await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
@ -55,12 +54,6 @@ test.describe('Onboarding tests', () => {
// *and* that the code is shown in the editor // *and* that the code is shown in the editor
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
// Make sure the model loaded
const XYPlanePoint = { x: 774, y: 116 } as const
const modelColor: [number, number, number] = [45, 45, 45]
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(8)
}) })
test( test(
@ -79,7 +72,7 @@ test.describe('Onboarding tests', () => {
const u = await getUtils(page) const u = await getUtils(page)
const viewportSize = { width: 1200, height: 1000 } const viewportSize = { width: 1200, height: 500 }
await page.setViewportSize(viewportSize) await page.setViewportSize(viewportSize)
await test.step(`Create a project and open to the onboarding`, async () => { await test.step(`Create a project and open to the onboarding`, async () => {
@ -99,14 +92,6 @@ test.describe('Onboarding tests', () => {
await expect(page.locator('.cm-content')).toContainText( await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket' '// Shelf Bracket'
) )
// TODO: jess make less shit
// Make sure the model loaded
//const XYPlanePoint = { x: 986, y: 522 } as const
//const modelColor: [number, number, number] = [76, 76, 76]
//await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
//await expectPixelColor(page, modelColor, XYPlanePoint, 8)
}) })
await electronApp.close() await electronApp.close()
@ -123,7 +108,7 @@ test.describe('Onboarding tests', () => {
}, initialCode) }, initialCode)
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 1000 }) await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
// Replay the onboarding // Replay the onboarding
@ -155,12 +140,6 @@ test.describe('Onboarding tests', () => {
return localStorage.getItem('persistCode') return localStorage.getItem('persistCode')
}) })
).toContain('// Shelf Bracket') ).toContain('// Shelf Bracket')
// Make sure the model loaded
const XYPlanePoint = { x: 986, y: 522 } as const
const modelColor: [number, number, number] = [76, 76, 76]
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
}) })
test('Click through each onboarding step', async ({ page }) => { test('Click through each onboarding step', async ({ page }) => {
@ -200,17 +179,6 @@ test.describe('Onboarding tests', () => {
// Test that the onboarding pane is gone // Test that the onboarding pane is gone
await expect(page.getByTestId('onboarding-content')).not.toBeVisible() await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect(page.url()).not.toContain('onboarding') await expect(page.url()).not.toContain('onboarding')
await u.openAndClearDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// TODO: jess to fix
// Make sure the model loaded
//const XYPlanePoint = { x: 774, y: 516 } as const
// const modelColor: [number, number, number] = [129, 129, 129]
// await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
// await expectPixelColor(page, modelColor, XYPlanePoint, 20)
}) })
test('Onboarding redirects and code updating', async ({ page }) => { test('Onboarding redirects and code updating', async ({ page }) => {
@ -425,7 +393,7 @@ test.describe('Onboarding tests', () => {
}) })
}) })
test.fixme( test(
'Restarting onboarding on desktop takes one attempt', 'Restarting onboarding on desktop takes one attempt',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browser: _ }, testInfo) => { async ({ browser: _ }, testInfo) => {
@ -471,7 +439,7 @@ test.fixme(
}) })
await test.step('Navigate into project', async () => { await test.step('Navigate into project', async () => {
await page.setViewportSize({ width: 1200, height: 1000 }) await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log) page.on('console', console.log)
@ -494,15 +462,7 @@ test.fixme(
await test.step('Confirm that the onboarding has restarted', async () => { await test.step('Confirm that the onboarding has restarted', async () => {
await expect(tutorialProjectIndicator).toBeVisible() await expect(tutorialProjectIndicator).toBeVisible()
await expect(tutorialModalText).toBeVisible() await expect(tutorialModalText).toBeVisible()
// Make sure the model loaded
const XYPlanePoint = { x: 988, y: 523 } as const
const modelColor: [number, number, number] = [76, 76, 76]
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
await tutorialDismissButton.click() await tutorialDismissButton.click()
// Make sure model still there.
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
}) })
await test.step('Clear code and restart onboarding from settings', async () => { await test.step('Clear code and restart onboarding from settings', async () => {

View File

@ -812,7 +812,6 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
commandName: 'Shell', commandName: 'Shell',
}) })
await clickOnCap() await clickOnCap()
await app.page.waitForTimeout(500)
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
@ -828,7 +827,6 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
} else { } else {
await test.step(`Preselect the cap`, async () => { await test.step(`Preselect the cap`, async () => {
await clickOnCap() await clickOnCap()
await app.page.waitForTimeout(500)
}) })
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => { await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {

View File

@ -950,75 +950,7 @@ test(
test.describe('Grid visibility', { tag: '@snapshot' }, () => { test.describe('Grid visibility', { tag: '@snapshot' }, () => {
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
// test.skip(process.platform === 'darwin', 'Skip on macos') test.skip(process.platform === 'darwin', 'Skip on macos')
test('Grid turned off to on via command bar', async ({ page }) => {
const u = await getUtils(page)
const stream = page.getByTestId('stream')
const mask = [
page.locator('#app-header'),
page.locator('#sidebar-top-ribbon'),
page.locator('#sidebar-bottom-ribbon'),
]
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
// wait for execution done
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(1)
await u.closeDebugPanel()
await u.closeKclCodePanel()
// TODO: Find a way to truly know that the objects have finished
// rendering, because an execution-done message is not sufficient.
await page.waitForTimeout(1000)
// Open the command bar.
await page
.getByRole('button', { name: 'Commands', exact: false })
.or(page.getByRole('button', { name: '⌘K' }))
.click()
const commandName = 'show scale grid'
const commandOption = page.getByRole('option', {
name: commandName,
exact: false,
})
const cmdSearchBar = page.getByPlaceholder('Search commands')
// This selector changes after we set the setting
await cmdSearchBar.fill(commandName)
await expect(commandOption).toBeVisible()
await commandOption.click()
const toggleInput = page.getByPlaceholder('Off')
await expect(toggleInput).toBeVisible()
await expect(toggleInput).toBeFocused()
// Select On
await page.keyboard.press('ArrowDown')
await expect(page.getByRole('option', { name: 'Off' })).toHaveAttribute(
'data-headlessui-state',
'active selected'
)
await page.keyboard.press('ArrowUp')
await expect(page.getByRole('option', { name: 'On' })).toHaveAttribute(
'data-headlessui-state',
'active'
)
await page.keyboard.press('Enter')
// Check the toast appeared
await expect(
page.getByText(`Set show scale grid to "true" as a user default`)
).toBeVisible()
await expect(stream).toHaveScreenshot({
maxDiffPixels: 100,
mask,
})
})
test('Grid turned off', async ({ page }) => { test('Grid turned off', async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -1164,109 +1096,3 @@ test.fixme('theme persists', async ({ page, context }) => {
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
}) })
test.describe('code color goober', { tag: '@snapshot' }, () => {
test('code color goober', async ({ page, context }) => {
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`// Create a pipe using a sweep.
// Create a path for the sweep.
sweepPath = startSketchOn('XZ')
|> startProfileAt([0.05, 0.05], %)
|> line([0, 7], %)
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> line([-3, 0], %)
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> line([0, 7], %)
sweepSketch = startSketchOn('XY')
|> startProfileAt([2, 0], %)
|> arc({
angleEnd = 360,
angleStart = 0,
radius = 2
}, %)
|> sweep({
path = sweepPath,
}, %)
|> appearance({
color = "#bb00ff",
metalness = 90,
roughness = 90
}, %)
`
)
})
await page.setViewportSize({ width: 1200, height: 1000 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await expect(page, 'expect small color widget').toHaveScreenshot({
maxDiffPixels: 100,
})
})
test('code color goober opening window', async ({ page, context }) => {
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`// Create a pipe using a sweep.
// Create a path for the sweep.
sweepPath = startSketchOn('XZ')
|> startProfileAt([0.05, 0.05], %)
|> line([0, 7], %)
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> line([-3, 0], %)
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> line([0, 7], %)
sweepSketch = startSketchOn('XY')
|> startProfileAt([2, 0], %)
|> arc({
angleEnd = 360,
angleStart = 0,
radius = 2
}, %)
|> sweep({
path = sweepPath,
}, %)
|> appearance({
color = "#bb00ff",
metalness = 90,
roughness = 90
}, %)
`
)
})
await page.setViewportSize({ width: 1200, height: 1000 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()
// Click the color widget
await page.locator('.cm-css-color-picker-wrapper input').click()
await expect(
page,
'expect small color widget to have window open'
).toHaveScreenshot({
maxDiffPixels: 100,
})
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -14,7 +14,7 @@ export const TEST_SETTINGS = {
}, },
modeling: { modeling: {
defaultUnit: 'in', defaultUnit: 'in',
mouseControls: 'Zoo', mouseControls: 'KittyCAD',
cameraProjection: 'perspective', cameraProjection: 'perspective',
showDebugPanel: true, showDebugPanel: true,
}, },

View File

@ -479,26 +479,4 @@ test.describe('Testing Camera Movement', () => {
}) })
} }
}) })
test('Right-click opens context menu when not dragged', async ({ page }) => {
const u = await getUtils(page)
await u.waitForAuthSkipAppStart()
await test.step(`The menu should not show if we drag the mouse`, async () => {
await page.mouse.move(900, 200)
await page.mouse.down({ button: 'right' })
await page.mouse.move(900, 300)
await page.mouse.up({ button: 'right' })
await expect(page.getByTestId('view-controls-menu')).not.toBeVisible()
})
await test.step(`The menu should show if we don't drag the mouse`, async () => {
await page.mouse.move(900, 200)
await page.mouse.down({ button: 'right' })
await page.mouse.up({ button: 'right' })
await expect(page.getByTestId('view-controls-menu')).toBeVisible()
})
})
}) })

View File

@ -26,17 +26,7 @@ test.describe('Testing constraints', () => {
}) })
const u = await getUtils(page) const u = await getUtils(page)
// constants and locators const PUR = 400 / 37.5 //pixeltoUnitRatio
const lengthValue = {
old: '20',
new: '25',
}
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
.getByRole('textbox')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
@ -46,26 +36,26 @@ test.describe('Testing constraints', () => {
await u.closeDebugPanel() await u.closeDebugPanel()
// Click the line of code for line. // Click the line of code for line.
// TODO remove this and reinstate `await topHorzSegmentClick()` await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.getByText(`line([0, ${lengthValue.old}], %)`).click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
// enter sketch again // enter sketch again
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500) // wait for animation await page.waitForTimeout(500) // wait for animation
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.keyboard.down('Shift')
await page.mouse.click(834, 244)
await page.keyboard.up('Shift')
await page await page
.getByRole('button', { name: 'dimension Length', exact: true }) .getByRole('button', { name: 'dimension Length', exact: true })
.click() .click()
await expect(cmdBarKclInput).toHaveText('20') await page.getByText('Add constraining value').click()
await cmdBarKclInput.fill(lengthValue.new)
await expect(
page.getByText(`Can't calculate`),
`Something went wrong with the KCL expression evaluation`
).not.toBeVisible()
await cmdBarSubmitButton.click()
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`length001 = ${lengthValue.new}sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)` `length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
) )
// Make sure we didn't pop out of sketch mode. // Make sure we didn't pop out of sketch mode.
@ -76,6 +66,7 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(500) // wait for animation await page.waitForTimeout(500) // wait for animation
// Exit sketch // Exit sketch
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.keyboard.press('Escape') await page.keyboard.press('Escape')
await expect( await expect(
page.getByRole('button', { name: 'Exit Sketch' }) page.getByRole('button', { name: 'Exit Sketch' })
@ -533,7 +524,7 @@ part002 = startSketchOn('XZ')
}) })
} }
}) })
test.describe('Test Angle constraint single selection', () => { test.describe('Test Angle/Length constraint single selection', () => {
const cases = [ const cases = [
{ {
testName: 'Angle - Add variable', testName: 'Angle - Add variable',
@ -547,6 +538,18 @@ part002 = startSketchOn('XZ')
constraint: 'angle', constraint: 'angle',
value: '83, 78.33', value: '83, 78.33',
}, },
{
testName: 'Length - Add variable',
addVariable: true,
constraint: 'length',
value: '83, length001',
},
{
testName: 'Length - No variable',
addVariable: false,
constraint: 'length',
value: '83, 78.33',
},
] as const ] as const
for (const { testName, addVariable, value, constraint } of cases) { for (const { testName, addVariable, value, constraint } of cases) {
test(`${testName}`, async ({ page }) => { test(`${testName}`, async ({ page }) => {
@ -605,90 +608,6 @@ part002 = startSketchOn('XZ')
}) })
} }
}) })
test.describe('Test Length constraint single selection', () => {
const cases = [
{
testName: 'Length - Add variable',
addVariable: true,
constraint: 'length',
value: '83, length001',
},
{
testName: 'Length - No variable',
addVariable: false,
constraint: 'length',
value: '83, 78.33',
},
] as const
for (const { testName, addVariable, value, constraint } of cases) {
test(`${testName}`, async ({ page }) => {
// constants and locators
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
.getByRole('textbox')
const cmdBarKclVariableNameInput =
page.getByPlaceholder('Variable name')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`yo = 5
part001 = startSketchOn('XZ')
|> startProfileAt([-7.54, -26.74], %)
|> line([74.36, 130.4], %)
|> line([78.92, -120.11], %)
|> line([9.16, 77.79], %)
|> line([51.19, 48.97], %)
part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %)
|> xLine(-425.34, %, $seg_what)
|> yLine(-264.06, %)
|> xLine(segLen(seg_what), %)
|> lineTo([profileStartX(%), profileStartY(%)], %)`
)
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await page.getByText('line([74.36, 130.4], %)').click()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
const line3 = await u.getSegmentBodyCoords(
`[data-overlay-index="${2}"]`
)
await page.mouse.click(line3.x, line3.y)
await page
.getByRole('button', {
name: 'Length: open menu',
})
.click()
await page.getByTestId('dropdown-constraint-' + constraint).click()
if (!addVariable) {
await test.step(`Clear the variable input`, async () => {
await cmdBarKclVariableNameInput.clear()
await cmdBarKclVariableNameInput.press('Backspace')
})
}
await expect(cmdBarKclInput).toHaveText('78.33')
await cmdBarSubmitButton.click()
const changedCode = `|> angledLine([${value}], %)`
await expect(page.locator('.cm-content')).toContainText(changedCode)
// checking active assures the cursor is where it should be
await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
})
}
})
test.describe('Many segments - no modal constraints', () => { test.describe('Many segments - no modal constraints', () => {
const cases = [ const cases = [
{ {
@ -949,15 +868,6 @@ part002 = startSketchOn('XZ')
|> line([3.13, -2.4], %)` |> line([3.13, -2.4], %)`
) )
}) })
// constants and locators
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
.getByRole('textbox')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
@ -1018,8 +928,8 @@ part002 = startSketchOn('XZ')
// await page.getByRole('button', { name: 'length', exact: true }).click() // await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByTestId('dropdown-constraint-length').click() await page.getByTestId('dropdown-constraint-length').click()
await cmdBarKclInput.fill('10') await page.getByLabel('length Value').fill('10')
await cmdBarSubmitButton.click() await page.getByRole('button', { name: 'Add constraining value' }).click()
activeLinesContent = await page.locator('.cm-activeLine').all() activeLinesContent = await page.locator('.cm-activeLine').all()
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`) await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)

View File

@ -91,14 +91,7 @@ test.describe('Testing segment overlays', () => {
await page.getByTestId('constraint-symbol-popover').count() await page.getByTestId('constraint-symbol-popover').count()
).toBeGreaterThan(0) ).toBeGreaterThan(0)
await unconstrainedLocator.click() await unconstrainedLocator.click()
await expect( await page.getByText('Add variable').click()
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await expect(page.locator('.cm-content')).toContainText(expectFinal) await expect(page.locator('.cm-content')).toContainText(expectFinal)
} }
@ -158,14 +151,7 @@ test.describe('Testing segment overlays', () => {
await page.getByTestId('constraint-symbol-popover').count() await page.getByTestId('constraint-symbol-popover').count()
).toBeGreaterThan(0) ).toBeGreaterThan(0)
await unconstrainedLocator.click() await unconstrainedLocator.click()
await expect( await page.getByText('Add variable').click()
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await expect(page.locator('.cm-content')).toContainText( await expect(page.locator('.cm-content')).toContainText(
expectAfterUnconstrained expectAfterUnconstrained
) )

View File

@ -1,9 +1,20 @@
import type { ForgeConfig } from '@electron-forge/shared-types' import type { ForgeConfig } from '@electron-forge/shared-types'
import { MakerSquirrel } from '@electron-forge/maker-squirrel'
import { MakerZIP } from '@electron-forge/maker-zip'
import { MakerDeb } from '@electron-forge/maker-deb'
import { MakerRpm } from '@electron-forge/maker-rpm'
import { VitePlugin } from '@electron-forge/plugin-vite' import { VitePlugin } from '@electron-forge/plugin-vite'
import { MakerWix, MakerWixConfig } from '@electron-forge/maker-wix'
import { FusesPlugin } from '@electron-forge/plugin-fuses' import { FusesPlugin } from '@electron-forge/plugin-fuses'
import { FuseV1Options, FuseVersion } from '@electron/fuses' import { FuseV1Options, FuseVersion } from '@electron/fuses'
import path from 'path' import path from 'path'
interface ExtendedMakerWixConfig extends MakerWixConfig {
// see https://github.com/electron/forge/issues/3673
// this is an undocumented property of electron-wix-msi
associateExtensions?: string
}
const rootDir = process.cwd() const rootDir = process.cwd()
const config: ForgeConfig = { const config: ForgeConfig = {
@ -28,7 +39,26 @@ const config: ForgeConfig = {
extendInfo: 'Info.plist', // Information for file associations. extendInfo: 'Info.plist', // Information for file associations.
}, },
rebuildConfig: {}, rebuildConfig: {},
makers: [], makers: [
new MakerSquirrel({
setupIcon: path.resolve(rootDir, 'assets', 'icon.ico'),
}),
new MakerWix({
icon: path.resolve(rootDir, 'assets', 'icon.ico'),
associateExtensions: 'kcl',
} as ExtendedMakerWixConfig),
new MakerZIP({}, ['darwin']),
new MakerRpm({
options: {
icon: path.resolve(rootDir, 'assets', 'icon.png'),
},
}),
new MakerDeb({
options: {
icon: path.resolve(rootDir, 'assets', 'icon.png'),
},
}),
],
plugins: [ plugins: [
new VitePlugin({ new VitePlugin({
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc. // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.

View File

@ -39,6 +39,7 @@
"chokidar": "^4.0.1", "chokidar": "^4.0.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"electron-squirrel-startup": "^1.0.1",
"electron-updater": "6.3.0", "electron-updater": "6.3.0",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"html2canvas-pro": "^1.5.8", "html2canvas-pro": "^1.5.8",
@ -68,7 +69,7 @@
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"scripts": { "scripts": {
"start": "vite --port=3000 --host=0.0.0.0", "start": "vite",
"start:prod": "vite preview --port=3000", "start:prod": "vite preview --port=3000",
"serve": "vite serve --port=3000", "serve": "vite serve --port=3000",
"build": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source \"$HOME/.cargo/env\" && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y && yarn build:wasm && vite build", "build": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source \"$HOME/.cargo/env\" && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y && yarn build:wasm && vite build",
@ -103,6 +104,8 @@
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts", "generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"tron:start": "electron-forge start", "tron:start": "electron-forge start",
"tron:package": "electron-forge package", "tron:package": "electron-forge package",
"tron:make": "electron-forge make",
"tron:publish": "electron-forge publish",
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron", "tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron",
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts", "tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
"tronb:package": "electron-builder --config electron-builder.yml", "tronb:package": "electron-builder --config electron-builder.yml",
@ -145,10 +148,17 @@
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.25.4", "@babel/preset-env": "^7.25.4",
"@electron-forge/cli": "7.4.0", "@electron-forge/cli": "^7.4.0",
"@electron-forge/plugin-fuses": "7.4.0", "@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/plugin-vite": "7.4.0", "@electron-forge/maker-rpm": "^7.4.0",
"@electron/fuses": "1.8.0", "@electron-forge/maker-squirrel": "^7.4.0",
"@electron-forge/maker-wix": "^7.5.0",
"@electron-forge/maker-zip": "^7.5.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
"@electron-forge/plugin-fuses": "^7.4.0",
"@electron-forge/plugin-vite": "^7.4.0",
"@electron/fuses": "^1.8.0",
"@electron/rebuild": "^3.6.0",
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@nabla/vite-plugin-eslint": "^2.0.5", "@nabla/vite-plugin-eslint": "^2.0.5",
@ -178,9 +188,9 @@
"@xstate/cli": "^0.5.17", "@xstate/cli": "^0.5.17",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"d3-force": "^3.0.0", "d3-force": "^3.0.0",
"electron": "32.1.2", "electron": "^32.1.2",
"electron-builder": "24.13.3", "electron-builder": "^24.13.3",
"electron-notarize": "1.2.2", "electron-notarize": "^1.2.2",
"eslint": "^8.0.1", "eslint": "^8.0.1",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-css-modules": "^2.12.0", "eslint-plugin-css-modules": "^2.12.0",

View File

@ -119,11 +119,6 @@
"title": "Pipe and Flange Assembly", "title": "Pipe and Flange Assembly",
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint." "description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
}, },
{
"file": "pipe-with-bend.kcl",
"title": "Pipe with bend",
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
},
{ {
"file": "poopy-shoe.kcl", "file": "poopy-shoe.kcl",
"title": "Poopy Shoe", "title": "Poopy Shoe",

View File

@ -105,7 +105,7 @@ export class CameraControls {
pendingZoom: number | null = null pendingZoom: number | null = null
pendingRotation: Vector2 | null = null pendingRotation: Vector2 | null = null
pendingPan: Vector2 | null = null pendingPan: Vector2 | null = null
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
isFovAnimationInProgress = false isFovAnimationInProgress = false
perspectiveFovBeforeOrtho = 45 perspectiveFovBeforeOrtho = 45
get isPerspective() { get isPerspective() {

View File

@ -505,8 +505,7 @@ const ConstraintSymbol = ({
constrainInfo: ConstrainInfo constrainInfo: ConstrainInfo
verticalPosition: 'top' | 'bottom' verticalPosition: 'top' | 'bottom'
}) => { }) => {
const { commandBarSend } = useCommandsContext() const { context, send } = useModelingContext()
const { context } = useModelingContext()
const varNameMap: { const varNameMap: {
[key in ConstrainInfo['type']]: { [key in ConstrainInfo['type']]: {
varName: string varName: string
@ -625,18 +624,11 @@ const ConstraintSymbol = ({
// disabled={implicitDesc} TODO why does this change styles that are hard to override? // disabled={implicitDesc} TODO why does this change styles that are hard to override?
onClick={toSync(async () => { onClick={toSync(async () => {
if (!isConstrained) { if (!isConstrained) {
commandBarSend({ send({
type: 'Find and select command', type: 'Convert to variable',
data: { data: {
name: 'Constrain with named value', pathToNode,
groupId: 'modeling', variableName: varName,
argDefaultValues: {
currentValue: {
pathToNode,
variableName: varName,
valueText: value,
},
},
}, },
}) })
} else if (isConstrained) { } else if (isConstrained) {

View File

@ -8,16 +8,11 @@ import { getSystemTheme } from 'lib/theme'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression' import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
import { roundOff } from 'lib/utils' import { roundOff } from 'lib/utils'
import { varMentions } from 'lib/varCompletionExtension' import { varMentions } from 'lib/varCompletionExtension'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import styles from './CommandBarKclInput.module.css' import styles from './CommandBarKclInput.module.css'
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst' import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor' import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
import { useSelector } from '@xstate/react'
const machineContextSelector = (snapshot?: {
context: Record<string, unknown>
}) => snapshot?.context
function CommandBarKclInput({ function CommandBarKclInput({
arg, arg,
@ -36,44 +31,12 @@ function CommandBarKclInput({
arg.name arg.name
] as KclCommandValue | undefined ] as KclCommandValue | undefined
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const argMachineContext = useSelector( const defaultValue = (arg.defaultValue as string) || ''
arg.machineActor,
machineContextSelector
)
const defaultValue = useMemo(
() =>
arg.defaultValue
? arg.defaultValue instanceof Function
? arg.defaultValue(commandBarState.context, argMachineContext)
: arg.defaultValue
: '',
[arg.defaultValue, commandBarState.context, argMachineContext]
)
const initialVariableName = useMemo(() => {
// Use the configured variable name if it exists
if (arg.variableName !== undefined) {
return arg.variableName instanceof Function
? arg.variableName(commandBarState.context, argMachineContext)
: arg.variableName
}
// or derive it from the previously set value or the argument name
return previouslySetValue && 'variableName' in previouslySetValue
? previouslySetValue.variableName
: arg.name
}, [
arg.variableName,
commandBarState.context,
argMachineContext,
arg.name,
previouslySetValue,
])
const [value, setValue] = useState( const [value, setValue] = useState(
previouslySetValue?.valueText || defaultValue || '' previouslySetValue?.valueText || defaultValue || ''
) )
const [createNewVariable, setCreateNewVariable] = useState( const [createNewVariable, setCreateNewVariable] = useState(
(previouslySetValue && 'variableName' in previouslySetValue) || previouslySetValue && 'variableName' in previouslySetValue
arg.createVariableByDefault ||
false
) )
const [canSubmit, setCanSubmit] = useState(true) const [canSubmit, setCanSubmit] = useState(true)
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
@ -89,7 +52,10 @@ function CommandBarKclInput({
isNewVariableNameUnique, isNewVariableNameUnique,
} = useCalculateKclExpression({ } = useCalculateKclExpression({
value, value,
initialVariableName, initialVariableName:
previouslySetValue && 'variableName' in previouslySetValue
? previouslySetValue.variableName
: arg.name,
}) })
const varMentionData: Completion[] = prevVariables.map((v) => ({ const varMentionData: Completion[] = prevVariables.map((v) => ({
label: v.key, label: v.key,

View File

@ -1,23 +1,13 @@
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { ActionIcon, ActionIconProps } from './ActionIcon' import { ActionIcon, ActionIconProps } from './ActionIcon'
import { import { RefObject, useEffect, useMemo, useRef, useState } from 'react'
MouseEvent,
RefObject,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { Dialog } from '@headlessui/react' import { Dialog } from '@headlessui/react'
export interface ContextMenuProps interface ContextMenuProps
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> { extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
items?: React.ReactElement[] items?: React.ReactElement[]
menuTargetElement?: RefObject<HTMLElement> menuTargetElement?: RefObject<HTMLElement>
guard?: (e: globalThis.MouseEvent) => boolean
event?: 'contextmenu' | 'mouseup'
} }
const DefaultContextMenuItems = [ const DefaultContextMenuItems = [
@ -30,8 +20,6 @@ export function ContextMenu({
items = DefaultContextMenuItems, items = DefaultContextMenuItems,
menuTargetElement, menuTargetElement,
className, className,
guard,
event = 'contextmenu',
...props ...props
}: ContextMenuProps) { }: ContextMenuProps) {
const dialogRef = useRef<HTMLDivElement>(null) const dialogRef = useRef<HTMLDivElement>(null)
@ -44,15 +32,6 @@ export function ContextMenu({
useHotkeys('esc', () => setOpen(false), { useHotkeys('esc', () => setOpen(false), {
enabled: open, enabled: open,
}) })
const handleContextMenu = useCallback(
(e: globalThis.MouseEvent) => {
if (guard && !guard(e)) return
e.preventDefault()
setPosition({ x: e.clientX, y: e.clientY })
setOpen(true)
},
[guard, setPosition, setOpen]
)
const dialogPositionStyle = useMemo(() => { const dialogPositionStyle = useMemo(() => {
if (!dialogRef.current) if (!dialogRef.current)
@ -99,9 +78,21 @@ export function ContextMenu({
// Add context menu listener to target once mounted // Add context menu listener to target once mounted
useEffect(() => { useEffect(() => {
menuTargetElement?.current?.addEventListener(event, handleContextMenu) const handleContextMenu = (e: MouseEvent) => {
console.log('context menu', e)
e.preventDefault()
setPosition({ x: e.x, y: e.y })
setOpen(true)
}
menuTargetElement?.current?.addEventListener(
'contextmenu',
handleContextMenu
)
return () => { return () => {
menuTargetElement?.current?.removeEventListener(event, handleContextMenu) menuTargetElement?.current?.removeEventListener(
'contextmenu',
handleContextMenu
)
} }
}, [menuTargetElement?.current]) }, [menuTargetElement?.current])
@ -109,10 +100,7 @@ export function ContextMenu({
<Dialog open={open} onClose={() => setOpen(false)}> <Dialog open={open} onClose={() => setOpen(false)}>
<div <div
className="fixed inset-0 z-50 w-screen h-screen" className="fixed inset-0 z-50 w-screen h-screen"
onContextMenu={(e) => { onContextMenu={(e) => e.preventDefault()}
e.preventDefault()
setPosition({ x: e.clientX, y: e.clientY })
}}
> >
<Dialog.Backdrop className="fixed z-10 inset-0" /> <Dialog.Backdrop className="fixed z-10 inset-0" />
<Dialog.Panel <Dialog.Panel

View File

@ -1,6 +1,6 @@
import { SceneInfra } from 'clientSideScene/sceneInfra' import { SceneInfra } from 'clientSideScene/sceneInfra'
import { sceneInfra } from 'lib/singletons' import { sceneInfra } from 'lib/singletons'
import { MutableRefObject, useEffect, useRef } from 'react' import { MutableRefObject, useEffect, useMemo, useRef } from 'react'
import { import {
WebGLRenderer, WebGLRenderer,
Scene, Scene,
@ -19,14 +19,16 @@ import {
Intersection, Intersection,
Object3D, Object3D,
} from 'three' } from 'three'
import {
ContextMenu,
ContextMenuDivider,
ContextMenuItem,
ContextMenuItemRefresh,
} from './ContextMenu'
import { Popover } from '@headlessui/react' import { Popover } from '@headlessui/react'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { import { useModelingContext } from 'hooks/useModelingContext'
useViewControlMenuItems,
ViewControlContextMenu,
} from './ViewControlMenu'
import { AxisNames } from 'lib/constants'
const CANVAS_SIZE = 80 const CANVAS_SIZE = 80
const FRUSTUM_SIZE = 0.5 const FRUSTUM_SIZE = 0.5
@ -38,14 +40,64 @@ enum AxisColors {
Z = '#6689ef', Z = '#6689ef',
Gray = '#c6c7c2', Gray = '#c6c7c2',
} }
enum AxisNames {
X = 'x',
Y = 'y',
Z = 'z',
NEG_X = '-x',
NEG_Y = '-y',
NEG_Z = '-z',
}
const axisNamesSemantic: Record<AxisNames, string> = {
[AxisNames.X]: 'Right',
[AxisNames.Y]: 'Back',
[AxisNames.Z]: 'Top',
[AxisNames.NEG_X]: 'Left',
[AxisNames.NEG_Y]: 'Front',
[AxisNames.NEG_Z]: 'Bottom',
}
export default function Gizmo() { export default function Gizmo() {
const menuItems = useViewControlMenuItems()
const wrapperRef = useRef<HTMLDivElement | null>(null) const wrapperRef = useRef<HTMLDivElement | null>(null)
const canvasRef = useRef<HTMLCanvasElement | null>(null) const canvasRef = useRef<HTMLCanvasElement | null>(null)
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null) const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
const cameraPassiveUpdateTimer = useRef(0) const cameraPassiveUpdateTimer = useRef(0)
const raycasterPassiveUpdateTimer = useRef(0) const raycasterPassiveUpdateTimer = useRef(0)
const { send: modelingSend } = useModelingContext()
const menuItems = useMemo(
() => [
...Object.entries(axisNamesSemantic).map(([axisName, axisSemantic]) => (
<ContextMenuItem
key={axisName}
onClick={() => {
sceneInfra.camControls
.updateCameraToAxis(axisName as AxisNames)
.catch(reportRejection)
}}
>
{axisSemantic} view
</ContextMenuItem>
)),
<ContextMenuDivider />,
<ContextMenuItem
onClick={() => {
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
}}
>
Reset view
</ContextMenuItem>,
<ContextMenuItem
onClick={() => {
modelingSend({ type: 'Center camera on selection' })
}}
>
Center view on selection
</ContextMenuItem>,
<ContextMenuDivider />,
<ContextMenuItemRefresh />,
],
[axisNamesSemantic]
)
useEffect(() => { useEffect(() => {
if (!canvasRef.current) return if (!canvasRef.current) return
@ -109,7 +161,7 @@ export default function Gizmo() {
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm" className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
> >
<canvas ref={canvasRef} /> <canvas ref={canvasRef} />
<ViewControlContextMenu menuTargetElement={wrapperRef} /> <ContextMenu menuTargetElement={wrapperRef} items={menuItems} />
</div> </div>
<GizmoDropdown items={menuItems} /> <GizmoDropdown items={menuItems} />
</div> </div>

View File

@ -1,4 +1,4 @@
import { APP_VERSION, getReleaseUrl } from 'routes/Settings' import { APP_VERSION } from 'routes/Settings'
import { CustomIcon } from 'components/CustomIcon' import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
@ -72,8 +72,10 @@ export function LowerRightControls({
<menu className="flex items-center justify-end gap-3 pointer-events-auto"> <menu className="flex items-center justify-end gap-3 pointer-events-auto">
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />} {!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
<a <a
onClick={openExternalBrowserIfDesktop(getReleaseUrl())} onClick={openExternalBrowserIfDesktop(
href={getReleaseUrl()} `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={'!no-underline font-mono text-xs ' + linkOverrideClassName} className={'!no-underline font-mono text-xs ' + linkOverrideClassName}

View File

@ -69,7 +69,14 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const [isKclLspReady, setIsKclLspReady] = useState(false) const [isKclLspReady, setIsKclLspReady] = useState(false)
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false) const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
const { auth } = useSettingsAuthContext() const {
auth,
settings: {
context: {
modeling: { defaultUnit },
},
},
} = useSettingsAuthContext()
const token = auth?.context.token const token = auth?.context.token
const navigate = useNavigate() const navigate = useNavigate()
@ -85,6 +92,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const initEvent: KclWorkerOptions = { const initEvent: KclWorkerOptions = {
wasmUrl: wasmUrl(), wasmUrl: wasmUrl(),
token: token, token: token,
baseUnit: defaultUnit.current,
apiBaseUrl: VITE_KC_API_BASE_URL, apiBaseUrl: VITE_KC_API_BASE_URL,
} }
lspWorker.postMessage({ lspWorker.postMessage({

View File

@ -41,10 +41,7 @@ import {
angleBetweenInfo, angleBetweenInfo,
applyConstraintAngleBetween, applyConstraintAngleBetween,
} from './Toolbar/SetAngleBetween' } from './Toolbar/SetAngleBetween'
import { import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
applyConstraintAngleLength,
applyConstraintLength,
} from './Toolbar/setAngleLength'
import { import {
canSweepSelection, canSweepSelection,
handleSelectionBatch, handleSelectionBatch,
@ -54,7 +51,6 @@ import {
Selections, Selections,
updateSelections, updateSelections,
canLoftSelection, canLoftSelection,
canRevolveSelection,
canShellSelection, canShellSelection,
} from 'lib/selections' } from 'lib/selections'
import { applyConstraintIntersect } from './Toolbar/Intersect' import { applyConstraintIntersect } from './Toolbar/Intersect'
@ -67,13 +63,12 @@ import {
getSketchOrientationDetails, getSketchOrientationDetails,
} from 'clientSideScene/sceneEntities' } from 'clientSideScene/sceneEntities'
import { import {
insertNamedConstant, moveValueIntoNewVariablePath,
replaceValueAtNodePath,
sketchOnExtrudedFace, sketchOnExtrudedFace,
sketchOnOffsetPlane, sketchOnOffsetPlane,
startSketchOnDefault, startSketchOnDefault,
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm' import { Program, parse, recast, resultIsOk } from 'lang/wasm'
import { import {
doesSceneHaveExtrudedSketch, doesSceneHaveExtrudedSketch,
doesSceneHaveSweepableSketch, doesSceneHaveSweepableSketch,
@ -86,6 +81,7 @@ import toast from 'react-hot-toast'
import { EditorSelection, Transaction } from '@codemirror/state' import { EditorSelection, Transaction } from '@codemirror/state'
import { useLoaderData, 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 { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { modelingMachineEvent } from 'editor/manager' import { modelingMachineEvent } from 'editor/manager'
@ -576,26 +572,6 @@ export const ModelingMachineProvider = ({
if (err(canSweep)) return false if (err(canSweep)) return false
return canSweep return canSweep
}, },
'has valid revolve selection': ({ context: { selectionRanges } }) => {
// A user can begin extruding if they either have 1+ faces selected or nothing selected
// TODO: I believe this guard only allows for extruding a single face at a time
const hasNoSelection =
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
if (hasNoSelection) {
// they have no selection, we should enable the button
// so they can select the face through the cmdbar
// BUT only if there's extrudable geometry
return doesSceneHaveSweepableSketch(kclManager.ast)
}
if (!isSketchPipe(selectionRanges)) return false
const canSweep = canRevolveSelection(selectionRanges)
if (err(canSweep)) return false
return canSweep
},
'has valid loft selection': ({ context: { selectionRanges } }) => { 'has valid loft selection': ({ context: { selectionRanges } }) => {
const hasNoSelection = const hasNoSelection =
selectionRanges.graphSelections.length === 0 || selectionRanges.graphSelections.length === 0 ||
@ -913,18 +889,12 @@ export const ModelingMachineProvider = ({
} }
} }
), ),
astConstrainLength: fromPromise( 'Get length info': fromPromise(
async ({ async ({ input: { selectionRanges, sketchDetails } }) => {
input: { selectionRanges, sketchDetails, lengthValue }, const { modifiedAst, pathToNodeMap } =
}) => { await applyConstraintAngleLength({
if (!lengthValue) selectionRanges,
return Promise.reject(new Error('No length value')) })
const constraintResult = await applyConstraintLength({
selectionRanges,
length: lengthValue,
})
if (err(constraintResult)) return Promise.reject(constraintResult)
const { modifiedAst, pathToNodeMap } = constraintResult
const pResult = parse(recast(modifiedAst)) const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult)) if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error')) return Promise.reject(new Error('Unexpected compilation error'))
@ -1093,88 +1063,38 @@ export const ModelingMachineProvider = ({
} }
} }
), ),
'Apply named value constraint': fromPromise( 'Get convert to variable info': fromPromise(
async ({ input: { selectionRanges, sketchDetails, data } }) => { async ({ input: { selectionRanges, sketchDetails, data } }) => {
if (!sketchDetails) { if (!sketchDetails)
return Promise.reject(new Error('No sketch details')) return Promise.reject(new Error('No sketch details'))
} const { variableName } = await getVarNameModal({
if (!data) { valueName: data?.variableName || 'var',
return Promise.reject(new Error('No data from command flow')) })
}
let pResult = parse(recast(kclManager.ast)) let pResult = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult)) if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error')) return Promise.reject(new Error('Unexpected compilation error'))
let parsed = pResult.program let parsed = pResult.program
let result: { const { modifiedAst: _modifiedAst, pathToReplacedNode } =
modifiedAst: Node<Program> moveValueIntoNewVariablePath(
pathToReplaced: PathToNode | null parsed,
} = { kclManager.programMemory,
modifiedAst: parsed, data?.pathToNode || [],
pathToReplaced: null, variableName
}
// If the user provided a constant name,
// we need to insert the named constant
// and then replace the node with the constant's name.
if ('variableName' in data.namedValue) {
const astAfterReplacement = replaceValueAtNodePath({
ast: parsed,
pathToNode: data.currentValue.pathToNode,
newExpressionString: data.namedValue.variableName,
})
if (trap(astAfterReplacement)) {
return Promise.reject(astAfterReplacement)
}
const parseResultAfterInsertion = parse(
recast(
insertNamedConstant({
node: astAfterReplacement.modifiedAst,
newExpression: data.namedValue,
})
)
) )
if ( pResult = parse(recast(_modifiedAst))
trap(parseResultAfterInsertion) ||
!resultIsOk(parseResultAfterInsertion)
)
return Promise.reject(parseResultAfterInsertion)
result = {
modifiedAst: parseResultAfterInsertion.program,
pathToReplaced: astAfterReplacement.pathToReplaced,
}
} else if ('valueText' in data.namedValue) {
// If they didn't provide a constant name,
// just replace the node with the value.
const astAfterReplacement = replaceValueAtNodePath({
ast: parsed,
pathToNode: data.currentValue.pathToNode,
newExpressionString: data.namedValue.valueText,
})
if (trap(astAfterReplacement)) {
return Promise.reject(astAfterReplacement)
}
// The `replacer` function returns a pathToNode that assumes
// an identifier is also being inserted into the AST, creating an off-by-one error.
// This corrects that error, but TODO we should fix this upstream
// to avoid this kind of error in the future.
astAfterReplacement.pathToReplaced[1][0] =
(astAfterReplacement.pathToReplaced[1][0] as number) - 1
result = astAfterReplacement
}
pResult = parse(recast(result.modifiedAst))
if (trap(pResult) || !resultIsOk(pResult)) if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error')) return Promise.reject(new Error('Unexpected compilation error'))
parsed = pResult.program parsed = pResult.program
if (trap(parsed)) return Promise.reject(parsed) if (trap(parsed)) return Promise.reject(parsed)
parsed = parsed as Node<Program> parsed = parsed as Node<Program>
if (!result.pathToReplaced) if (!pathToReplacedNode)
return Promise.reject(new Error('No path to replaced node')) return Promise.reject(new Error('No path to replaced node'))
const updatedAst = const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch( await sceneEntitiesManager.updateAstAndRejigSketch(
result.pathToReplaced || [], pathToReplacedNode || [],
parsed, parsed,
sketchDetails.zAxis, sketchDetails.zAxis,
sketchDetails.yAxis, sketchDetails.yAxis,
@ -1187,7 +1107,7 @@ export const ModelingMachineProvider = ({
) )
const selection = updateSelections( const selection = updateSelections(
{ 0: result.pathToReplaced }, { 0: pathToReplacedNode },
selectionRanges, selectionRanges,
updatedAst.newAst updatedAst.newAst
) )
@ -1195,7 +1115,7 @@ export const ModelingMachineProvider = ({
return { return {
selectionType: 'completeSelection', selectionType: 'completeSelection',
selection, selection,
updatedPathToNode: result.pathToReplaced, updatedPathToNode: pathToReplacedNode,
} }
} }
), ),

View File

@ -6,7 +6,6 @@ import Tooltip from 'components/Tooltip'
import { CustomIconName } from 'components/CustomIcon' import { CustomIconName } from 'components/CustomIcon'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons' import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from 'components/ActionIcon' import { ActionIcon } from 'components/ActionIcon'
import { onboardingPaths } from 'routes/Onboarding/paths'
export interface ModelingPaneProps { export interface ModelingPaneProps {
id: string id: string
@ -71,13 +70,13 @@ export const ModelingPane = ({
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus const onboardingStatus = settings.context.app.onboardingStatus
const pointerEventsCssClass = const pointerEventsCssClass =
onboardingStatus.current === onboardingPaths.CAMERA onboardingStatus.current === 'camera'
? 'pointer-events-none ' ? 'pointer-events-none '
: 'pointer-events-auto ' : 'pointer-events-auto '
return ( return (
<section <section
{...props} {...props}
aria-label={title && typeof title === 'string' ? title : ''} title={title && typeof title === 'string' ? title : ''}
data-testid={detailsTestId} data-testid={detailsTestId}
id={id} id={id}
className={ className={

View File

@ -19,7 +19,6 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons' import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { useKclContext } from 'lang/KclProvider' import { useKclContext } from 'lang/KclProvider'
import { MachineManagerContext } from 'components/MachineManagerProvider' import { MachineManagerContext } from 'components/MachineManagerProvider'
import { onboardingPaths } from 'routes/Onboarding/paths'
interface ModelingSidebarProps { interface ModelingSidebarProps {
paneOpacity: '' | 'opacity-20' | 'opacity-40' paneOpacity: '' | 'opacity-20' | 'opacity-40'
@ -42,7 +41,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
const onboardingStatus = settings.context.app.onboardingStatus const onboardingStatus = settings.context.app.onboardingStatus
const { send, context } = useModelingContext() const { send, context } = useModelingContext()
const pointerEventsCssClass = const pointerEventsCssClass =
onboardingStatus.current === onboardingPaths.CAMERA || onboardingStatus.current === 'camera' ||
context.store?.openPanes.length === 0 context.store?.openPanes.length === 0
? 'pointer-events-none ' ? 'pointer-events-none '
: 'pointer-events-auto ' : 'pointer-events-auto '

View File

@ -10,7 +10,7 @@ interface AllKeybindingsFieldsProps {}
export const AllKeybindingsFields = forwardRef( export const AllKeybindingsFields = forwardRef(
( (
_props: AllKeybindingsFieldsProps, props: AllKeybindingsFieldsProps,
scrollRef: ForwardedRef<HTMLDivElement> scrollRef: ForwardedRef<HTMLDivElement>
) => { ) => {
// This is how we will get the interaction map from the context // This is how we will get the interaction map from the context
@ -25,7 +25,7 @@ export const AllKeybindingsFields = forwardRef(
.map(([category, categoryItems]) => ( .map(([category, categoryItems]) => (
<div className="flex flex-col gap-4 px-2 pr-4"> <div className="flex flex-col gap-4 px-2 pr-4">
<h2 <h2
id={`category-${category.replaceAll(/\s/g, '-')}`} id={`category-${category}`}
className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold" className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold"
> >
{category} {category}

View File

@ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
import { SettingsFieldInput } from './SettingsFieldInput' import { SettingsFieldInput } from './SettingsFieldInput'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { APP_VERSION, IS_NIGHTLY, getReleaseUrl } from 'routes/Settings' import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { import {
createAndOpenNewTutorialProject, createAndOpenNewTutorialProject,
@ -246,8 +246,10 @@ export const AllSettingsFields = forwardRef(
to inject the version from package.json */} to inject the version from package.json */}
App version {APP_VERSION}.{' '} App version {APP_VERSION}.{' '}
<a <a
onClick={openExternalBrowserIfDesktop(getReleaseUrl())} onClick={openExternalBrowserIfDesktop(
href={getReleaseUrl()} `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -269,7 +271,7 @@ export const AllSettingsFields = forwardRef(
, and start a discussion if you don't see it! Your feedback will , and start a discussion if you don't see it! Your feedback will
help us prioritize what to build next. help us prioritize what to build next.
</p> </p>
{!IS_NIGHTLY && ( {PACKAGE_NAME.indexOf('-nightly') === -1 && (
<p className="max-w-2xl mt-6"> <p className="max-w-2xl mt-6">
Want to experience the latest and (hopefully) greatest from our Want to experience the latest and (hopefully) greatest from our
main development branch?{' '} main development branch?{' '}

View File

@ -19,7 +19,7 @@ export function KeybindingsSectionsList({
key={category} key={category}
onClick={() => onClick={() =>
scrollRef.current scrollRef.current
?.querySelector(`#category-${category.replaceAll(/\s/g, '-')}`) ?.querySelector(`#category-${category}`)
?.scrollIntoView({ ?.scrollIntoView({
block: 'center', block: 'center',
behavior: 'smooth', behavior: 'smooth',

View File

@ -1,5 +1,5 @@
import { trap } from 'lib/trap' import { trap } from 'lib/trap'
import { useMachine, useSelector } from '@xstate/react' import { useMachine } from '@xstate/react'
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom' import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
import { PATHS, BROWSER_PATH } from 'lib/paths' import { PATHS, BROWSER_PATH } from 'lib/paths'
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine' import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
@ -23,6 +23,7 @@ import {
engineCommandManager, engineCommandManager,
sceneEntitiesManager, sceneEntitiesManager,
} from 'lib/singletons' } from 'lib/singletons'
import { uuidv4 } from 'lib/utils'
import { IndexLoaderData } from 'lib/types' import { IndexLoaderData } from 'lib/types'
import { settings } from 'lib/settings/initialSettings' import { settings } from 'lib/settings/initialSettings'
import { import {
@ -54,15 +55,11 @@ type SettingsAuthContextType = {
settings: MachineContext<typeof settingsMachine> settings: MachineContext<typeof settingsMachine>
} }
/** // a little hacky for sure, open to changing it
* This variable is used to store the last snapshot of the settings context // this implies that we should only even have one instance of this provider mounted at any one time
* for use outside of React, such as in `wasm.ts`. It is updated every time // but I think that's a safe assumption
* the settings machine changes with `useSelector`. let settingsStateRef: ContextFrom<typeof settingsMachine> | undefined
* TODO: when we decouple XState from React, we can just subscribe to the actor directly from `wasm.ts` export const getSettingsState = () => settingsStateRef
*/
export let lastSettingsContextSnapshot:
| ContextFrom<typeof settingsMachine>
| undefined
export const SettingsAuthContext = createContext({} as SettingsAuthContextType) export const SettingsAuthContext = createContext({} as SettingsAuthContextType)
@ -132,11 +129,27 @@ export const SettingsAuthProviderBase = ({
.setTheme(context.app.theme.current) .setTheme(context.app.theme.current)
.catch(reportRejection) .catch(reportRejection)
}, },
setEngineScaleGridVisibility: ({ context }) => {
engineCommandManager.setScaleGridVisibility(
context.modeling.showScaleGrid.current
)
},
setClientTheme: ({ context }) => { setClientTheme: ({ context }) => {
const opposingTheme = getOppositeTheme(context.app.theme.current) const opposingTheme = getOppositeTheme(context.app.theme.current)
sceneInfra.theme = opposingTheme sceneInfra.theme = opposingTheme
sceneEntitiesManager.updateSegmentBaseColor(opposingTheme) sceneEntitiesManager.updateSegmentBaseColor(opposingTheme)
}, },
setEngineEdges: ({ context }) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.sendSceneCommand({
cmd_id: uuidv4(),
type: 'modeling_cmd_req',
cmd: {
type: 'edge_lines_visible' as any, // TODO update kittycad.ts to get this new command type
hidden: !context.modeling.highlightEdges.current,
},
})
},
toastSuccess: ({ event }) => { toastSuccess: ({ event }) => {
if (!('data' in event)) return if (!('data' in event)) return
const eventParts = event.type.replace(/^set./, '').split('.') as [ const eventParts = event.type.replace(/^set./, '').split('.') as [
@ -162,27 +175,17 @@ export const SettingsAuthProviderBase = ({
}, },
'Execute AST': ({ context, event }) => { 'Execute AST': ({ context, event }) => {
try { try {
const relevantSetting = (s: typeof settings) => {
return (
s.modeling?.defaultUnit?.current !==
context.modeling.defaultUnit.current ||
s.modeling.showScaleGrid.current !==
context.modeling.showScaleGrid.current ||
s.modeling?.highlightEdges.current !==
context.modeling.highlightEdges.current
)
}
const allSettingsIncludesUnitChange = const allSettingsIncludesUnitChange =
event.type === 'Set all settings' && event.type === 'Set all settings' &&
relevantSetting(event.settings) event.settings?.modeling?.defaultUnit?.current !==
context.modeling.defaultUnit.current
const resetSettingsIncludesUnitChange = const resetSettingsIncludesUnitChange =
event.type === 'Reset settings' && relevantSetting(settings) event.type === 'Reset settings' &&
context.modeling.defaultUnit.current !==
settings?.modeling?.defaultUnit?.default
if ( if (
event.type === 'set.modeling.defaultUnit' || event.type === 'set.modeling.defaultUnit' ||
event.type === 'set.modeling.showScaleGrid' ||
event.type === 'set.modeling.highlightEdges' ||
allSettingsIncludesUnitChange || allSettingsIncludesUnitChange ||
resetSettingsIncludesUnitChange resetSettingsIncludesUnitChange
) { ) {
@ -211,10 +214,7 @@ export const SettingsAuthProviderBase = ({
}), }),
{ input: loadedSettings } { input: loadedSettings }
) )
// Any time the actor changes, update the settings state for external use settingsStateRef = settingsState.context
useSelector(settingsActor, (s) => {
lastSettingsContextSnapshot = s.context
})
useEffect(() => { useEffect(() => {
if (!isDesktop()) return if (!isDesktop()) return

View File

@ -20,7 +20,6 @@ import { IndexLoaderData } from 'lib/types'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { err, reportRejection } from 'lib/trap' import { err, reportRejection } from 'lib/trap'
import { getArtifactOfTypes } from 'lang/std/artifactGraph' import { getArtifactOfTypes } from 'lang/std/artifactGraph'
import { ViewControlContextMenu } from './ViewControlMenu'
enum StreamState { enum StreamState {
Playing = 'playing', Playing = 'playing',
@ -31,7 +30,6 @@ enum StreamState {
export const Stream = () => { export const Stream = () => {
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const videoWrapperRef = useRef<HTMLDivElement>(null)
const videoRef = useRef<HTMLVideoElement>(null) const videoRef = useRef<HTMLVideoElement>(null)
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const { state, send } = useModelingContext() const { state, send } = useModelingContext()
@ -260,7 +258,7 @@ export const Stream = () => {
setIsLoading(false) setIsLoading(false)
}, [mediaStream]) }, [mediaStream])
const handleClick: MouseEventHandler<HTMLDivElement> = (e) => { const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
// If we've got no stream or connection, don't do anything // 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
@ -322,11 +320,10 @@ export const Stream = () => {
return ( return (
<div <div
ref={videoWrapperRef}
className="absolute inset-0 z-0" className="absolute inset-0 z-0"
id="stream" id="stream"
data-testid="stream" data-testid="stream"
onClick={handleClick} onClick={handleMouseUp}
onDoubleClick={enterSketchModeIfSelectingSketch} onDoubleClick={enterSketchModeIfSelectingSketch}
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
onContextMenuCapture={(e) => e.preventDefault()} onContextMenuCapture={(e) => e.preventDefault()}
@ -387,14 +384,6 @@ export const Stream = () => {
</Loading> </Loading>
</div> </div>
)} )}
<ViewControlContextMenu
event="mouseup"
guard={(e) =>
sceneInfra.camControls.wasDragging === false &&
btnName(e).right === true
}
menuTargetElement={videoWrapperRef}
/>
</div> </div>
) )
} }

View File

@ -2,7 +2,6 @@ import toast from 'react-hot-toast'
import { ActionButton } from './ActionButton' import { ActionButton } from './ActionButton'
import { openExternalBrowserIfDesktop } from 'lib/openWindow' import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { Marked } from '@ts-stack/markdown' import { Marked } from '@ts-stack/markdown'
import { getReleaseUrl } from 'routes/Settings'
export function ToastUpdate({ export function ToastUpdate({
version, version,
@ -33,8 +32,10 @@ export function ToastUpdate({
A new update has downloaded and will be available next time you A new update has downloaded and will be available next time you
start the app. You can view the release notes{' '} start the app. You can view the release notes{' '}
<a <a
onClick={openExternalBrowserIfDesktop(getReleaseUrl(version))} onClick={openExternalBrowserIfDesktop(
href={getReleaseUrl(version)} `https://github.com/KittyCAD/modeling-app/releases/tag/v${version}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${version}`}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >

View File

@ -22,7 +22,6 @@ import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { normaliseAngle } from '../../lib/utils' import { normaliseAngle } from '../../lib/utils'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { KclCommandValue } from 'lib/commandTypes'
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal) const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
@ -64,57 +63,6 @@ export function angleLengthInfo({
return { enabled, transforms } return { enabled, transforms }
} }
export async function applyConstraintLength({
length,
selectionRanges,
}: {
length: KclCommandValue
selectionRanges: Selections
}) {
const ast = kclManager.ast
const angleLength = angleLengthInfo({ selectionRanges })
if (err(angleLength)) return angleLength
const { transforms } = angleLength
let distanceExpression: Expr = length.valueAst
/**
* To be "constrained", the value must be a binary expression, a named value, or a function call.
* If it has a variable name, we need to insert a variable declaration at the correct index.
*/
if (
'variableName' in length &&
length.variableName &&
length.insertIndex !== undefined
) {
const newBody = [...ast.body]
newBody.splice(length.insertIndex, 0, length.variableDeclarationAst)
ast.body = newBody
distanceExpression = createIdentifier(length.variableName)
}
if (!isExprBinaryPart(distanceExpression)) {
return new Error('Invalid valueNode, is not a BinaryPart')
}
const retval = transformAstSketchLines({
ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: distanceExpression,
})
if (err(retval)) return Promise.reject(retval)
const { modifiedAst: _modifiedAst, pathToNodeMap } = retval
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
}
}
export async function applyConstraintAngleLength({ export async function applyConstraintAngleLength({
selectionRanges, selectionRanges,
angleOrLength = 'setLength', angleOrLength = 'setLength',

View File

@ -41,10 +41,7 @@ export function UnitsMenu() {
close() close()
}} }}
> >
<span className="flex-1">{baseUnitLabels[unit]}</span> {baseUnitLabels[unit]}
{unit === settings.context.modeling.defaultUnit.current && (
<span className="text-chalkboard-60">current</span>
)}
</button> </button>
</li> </li>
))} ))}

View File

@ -1,66 +0,0 @@
import { reportRejection } from 'lib/trap'
import {
ContextMenu,
ContextMenuDivider,
ContextMenuItem,
ContextMenuItemRefresh,
ContextMenuProps,
} from './ContextMenu'
import { AxisNames, VIEW_NAMES_SEMANTIC } from 'lib/constants'
import { useModelingContext } from 'hooks/useModelingContext'
import { useMemo } from 'react'
import { sceneInfra } from 'lib/singletons'
export function useViewControlMenuItems() {
const { send: modelingSend } = useModelingContext()
const menuItems = useMemo(
() => [
...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => (
<ContextMenuItem
key={axisName}
onClick={() => {
sceneInfra.camControls
.updateCameraToAxis(axisName as AxisNames)
.catch(reportRejection)
}}
>
{axisSemantic} view
</ContextMenuItem>
)),
<ContextMenuDivider />,
<ContextMenuItem
onClick={() => {
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
}}
>
Reset view
</ContextMenuItem>,
<ContextMenuItem
onClick={() => {
modelingSend({ type: 'Center camera on selection' })
}}
>
Center view on selection
</ContextMenuItem>,
<ContextMenuDivider />,
<ContextMenuItemRefresh />,
],
[VIEW_NAMES_SEMANTIC]
)
return menuItems
}
export function ViewControlContextMenu({
menuTargetElement: wrapperRef,
...props
}: ContextMenuProps) {
const menuItems = useViewControlMenuItems()
return (
<ContextMenu
data-testid="view-controls-menu"
menuTargetElement={wrapperRef}
items={menuItems}
{...props}
/>
)
}

View File

@ -1,327 +0,0 @@
import {
EditorView,
WidgetType,
ViewUpdate,
ViewPlugin,
DecorationSet,
Decoration,
} from '@codemirror/view'
import { Range, Extension, Text } from '@codemirror/state'
import { NodeProp, Tree } from '@lezer/common'
import { language, syntaxTree } from '@codemirror/language'
interface PickerState {
from: number
to: number
alpha: string
colorType: ColorType
}
export interface WidgetOptions extends PickerState {
color: string
}
export type ColorData = Omit<WidgetOptions, 'from' | 'to'>
const pickerState = new WeakMap<HTMLInputElement, PickerState>()
export enum ColorType {
hex = 'HEX',
}
const hexRegex = /(^|\b)(#[0-9a-f]{3,9})(\b|$)/i
function discoverColorsInKCL(
syntaxTree: Tree,
from: number,
to: number,
typeName: string,
doc: Text,
language?: string
): WidgetOptions | Array<WidgetOptions> | null {
switch (typeName) {
case 'Program':
case 'VariableDeclaration':
case 'CallExpression':
case 'ObjectExpression':
case 'ObjectProperty':
case 'ArgumentList':
case 'PipeExpression': {
let innerTree = syntaxTree.resolveInner(from, 0).tree
if (!innerTree) {
innerTree = syntaxTree.resolveInner(from, 1).tree
if (!innerTree) {
return null
}
}
const overlayTree = innerTree.prop(NodeProp.mounted)?.tree
if (overlayTree?.type.name !== 'Styles') {
return null
}
const ret: Array<WidgetOptions> = []
overlayTree.iterate({
from: 0,
to: overlayTree.length,
enter: ({ type, from: overlayFrom, to: overlayTo }) => {
const maybeWidgetOptions = discoverColorsInKCL(
syntaxTree,
// We add one because the tree doesn't include the
// quotation mark from the style tag
from + 1 + overlayFrom,
from + 1 + overlayTo,
type.name,
doc,
language
)
if (maybeWidgetOptions) {
if (Array.isArray(maybeWidgetOptions)) {
console.error('Unexpected nested overlays')
ret.push(...maybeWidgetOptions)
} else {
ret.push(maybeWidgetOptions)
}
}
},
})
return ret
}
case 'String': {
const result = parseColorLiteral(doc.sliceString(from, to))
if (!result) {
return null
}
return {
...result,
from,
to,
}
}
default:
return null
}
}
export function parseColorLiteral(colorLiteral: string): ColorData | null {
const literal = colorLiteral.replace(/"/g, '')
const match = hexRegex.exec(literal)
if (!match) {
return null
}
const [color, alpha] = toFullHex(literal)
return {
colorType: ColorType.hex,
color,
alpha,
}
}
function colorPickersDecorations(
view: EditorView,
discoverColors: typeof discoverColorsInKCL
) {
const widgets: Array<Range<Decoration>> = []
const st = syntaxTree(view.state)
for (const range of view.visibleRanges) {
st.iterate({
from: range.from,
to: range.to,
enter: ({ type, from, to }) => {
const maybeWidgetOptions = discoverColors(
st,
from,
to,
type.name,
view.state.doc,
view.state.facet(language)?.name
)
if (!maybeWidgetOptions) {
return
}
if (!Array.isArray(maybeWidgetOptions)) {
widgets.push(
Decoration.widget({
widget: new ColorPickerWidget(maybeWidgetOptions),
side: 1,
}).range(maybeWidgetOptions.from)
)
return
}
for (const wo of maybeWidgetOptions) {
widgets.push(
Decoration.widget({
widget: new ColorPickerWidget(wo),
side: 1,
}).range(wo.from)
)
}
},
})
}
return Decoration.set(widgets)
}
function toFullHex(color: string): string[] {
if (color.length === 4) {
// 3-char hex
return [
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
'',
]
}
if (color.length === 5) {
// 4-char hex (alpha)
return [
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
color[4].repeat(2),
]
}
if (color.length === 9) {
// 8-char hex (alpha)
return [`#${color.slice(1, -2)}`, color.slice(-2)]
}
return [color, '']
}
export const wrapperClassName = 'cm-css-color-picker-wrapper'
class ColorPickerWidget extends WidgetType {
private readonly state: PickerState
private readonly color: string
constructor({ color, ...state }: WidgetOptions) {
super()
this.state = state
this.color = color
}
eq(other: ColorPickerWidget) {
return (
other.state.colorType === this.state.colorType &&
other.color === this.color &&
other.state.from === this.state.from &&
other.state.to === this.state.to &&
other.state.alpha === this.state.alpha
)
}
toDOM() {
const picker = document.createElement('input')
pickerState.set(picker, this.state)
picker.type = 'color'
picker.value = this.color
const wrapper = document.createElement('span')
wrapper.appendChild(picker)
wrapper.className = wrapperClassName
return wrapper
}
ignoreEvent() {
return false
}
}
export const colorPickerTheme = EditorView.baseTheme({
[`.${wrapperClassName}`]: {
display: 'inline-block',
outline: '1px solid #eee',
marginRight: '0.6ch',
height: '1em',
width: '1em',
transform: 'translateY(1px)',
},
[`.${wrapperClassName} input[type="color"]`]: {
cursor: 'pointer',
height: '100%',
width: '100%',
padding: 0,
border: 'none',
'&::-webkit-color-swatch-wrapper': {
padding: 0,
},
'&::-webkit-color-swatch': {
border: 'none',
},
'&::-moz-color-swatch': {
border: 'none',
},
},
})
interface IFactoryOptions {
discoverColors: typeof discoverColorsInKCL
}
export const makeColorPicker = (options: IFactoryOptions) =>
ViewPlugin.fromClass(
class ColorPickerViewPlugin {
decorations: DecorationSet
constructor(view: EditorView) {
this.decorations = colorPickersDecorations(view, options.discoverColors)
}
update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) {
this.decorations = colorPickersDecorations(
update.view,
options.discoverColors
)
}
}
},
{
decorations: (v) => v.decorations,
eventHandlers: {
change: (e, view) => {
const target = e.target as HTMLInputElement
if (
target.nodeName !== 'INPUT' ||
!target.parentElement ||
!target.parentElement.classList.contains(wrapperClassName)
) {
return false
}
const data = pickerState.get(target)!
let converted = '"' + target.value + data.alpha + '"'
view.dispatch({
changes: {
from: data.from,
to: data.to,
insert: converted,
},
})
return true
},
},
}
)
export const colorPicker: Extension = [
makeColorPicker({ discoverColors: discoverColorsInKCL }),
colorPickerTheme,
]

View File

@ -17,7 +17,6 @@ import { kclPlugin } from '.'
import type * as LSP from 'vscode-languageserver-protocol' import type * as LSP from 'vscode-languageserver-protocol'
// @ts-ignore: No types available // @ts-ignore: No types available
import { parser } from './kcl.grammar' import { parser } from './kcl.grammar'
import { colorPicker } from './colors'
export interface LanguageOptions { export interface LanguageOptions {
workspaceFolders: LSP.WorkspaceFolder[] workspaceFolders: LSP.WorkspaceFolder[]
@ -55,14 +54,14 @@ export const KclLanguage = LRLanguage.define({
}) })
export function kcl(options: LanguageOptions) { export function kcl(options: LanguageOptions) {
return new LanguageSupport(KclLanguage, [ return new LanguageSupport(
colorPicker, KclLanguage,
kclPlugin({ kclPlugin({
documentUri: options.documentUri, documentUri: options.documentUri,
workspaceFolders: options.workspaceFolders, workspaceFolders: options.workspaceFolders,
allowHTMLContent: true, allowHTMLContent: true,
client: options.client, client: options.client,
processLspNotification: options.processLspNotification, processLspNotification: options.processLspNotification,
}), })
]) )
} }

View File

@ -1,5 +1,7 @@
import { LspWorkerEventType } from '@kittycad/codemirror-lsp-client' import { LspWorkerEventType } from '@kittycad/codemirror-lsp-client'
import { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
export enum LspWorker { export enum LspWorker {
Kcl = 'kcl', Kcl = 'kcl',
Copilot = 'copilot', Copilot = 'copilot',
@ -7,6 +9,7 @@ export enum LspWorker {
export interface KclWorkerOptions { export interface KclWorkerOptions {
wasmUrl: string wasmUrl: string
token: string token: string
baseUnit: UnitLength
apiBaseUrl: string apiBaseUrl: string
} }

View File

@ -17,6 +17,7 @@ import {
KclWorkerOptions, KclWorkerOptions,
CopilotWorkerOptions, CopilotWorkerOptions,
} from 'editor/plugins/lsp/types' } from 'editor/plugins/lsp/types'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { err, reportRejection } from 'lib/trap' import { err, reportRejection } from 'lib/trap'
const intoServer: IntoServer = new IntoServer() const intoServer: IntoServer = new IntoServer()
@ -45,12 +46,14 @@ export async function copilotLspRun(
export async function kclLspRun( export async function kclLspRun(
config: ServerConfig, config: ServerConfig,
engineCommandManager: EngineCommandManager | null,
token: string, token: string,
baseUnit: string,
baseUrl: string baseUrl: string
) { ) {
try { try {
console.log('start kcl lsp') console.log('start kcl lsp')
await kcl_lsp_run(config, null, undefined, token, baseUrl) await kcl_lsp_run(config, engineCommandManager, baseUnit, token, baseUrl)
} catch (e: any) { } catch (e: any) {
console.log('kcl lsp failed', e) console.log('kcl lsp failed', e)
// We can't restart here because a moved value, we should do this another way. // We can't restart here because a moved value, we should do this another way.
@ -79,7 +82,13 @@ onmessage = function (event: MessageEvent) {
switch (worker) { switch (worker) {
case LspWorker.Kcl: case LspWorker.Kcl:
const kclData = eventData as KclWorkerOptions const kclData = eventData as KclWorkerOptions
await kclLspRun(config, kclData.token, kclData.apiBaseUrl) await kclLspRun(
config,
null,
kclData.token,
kclData.baseUnit,
kclData.apiBaseUrl
)
break break
case LspWorker.Copilot: case LspWorker.Copilot:
let copilotData = eventData as CopilotWorkerOptions let copilotData = eventData as CopilotWorkerOptions

View File

@ -2,7 +2,7 @@ import { useLayoutEffect, useEffect, useRef } from 'react'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { deferExecution } from 'lib/utils' import { deferExecution } from 'lib/utils'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
import { makeDefaultPlanes } from 'lang/wasm' import { makeDefaultPlanes, modifyGrid } from 'lang/wasm'
import { useModelingContext } from './useModelingContext' import { useModelingContext } from './useModelingContext'
import { useNetworkContext } from 'hooks/useNetworkContext' import { useNetworkContext } from 'hooks/useNetworkContext'
import { useAppState, useAppStream } from 'AppState' import { useAppState, useAppStream } from 'AppState'
@ -56,6 +56,9 @@ export function useSetupEngineManager(
makeDefaultPlanes: () => { makeDefaultPlanes: () => {
return makeDefaultPlanes(kclManager.engineCommandManager) return makeDefaultPlanes(kclManager.engineCommandManager)
}, },
modifyGrid: (hidden: boolean) => {
return modifyGrid(kclManager.engineCommandManager, hidden)
},
}) })
hasSetNonZeroDimensions.current = true hasSetNonZeroDimensions.current = true
} }

View File

@ -24,8 +24,6 @@ export function useConvertToVariable(range?: SourceRange) {
}, [enable]) }, [enable])
useEffect(() => { useEffect(() => {
// Return early if there are no selection ranges for whatever reason
if (!context.selectionRanges) return
const parsed = ast const parsed = ast
const meta = isNodeSafeToReplace( const meta = isNodeSafeToReplace(

View File

@ -317,8 +317,3 @@ code {
#code-mirror-override .cm-editor { #code-mirror-override .cm-editor {
height: 100% !important; height: 100% !important;
} }
/* Can't use #code-mirror-override here as we're outside of this div */
.body-bg .cm-diagnosticAction {
@apply bg-primary;
}

View File

@ -311,6 +311,8 @@ export class KclManager {
// Do not send send scene commands if the program was interrupted, go to clean up // Do not send send scene commands if the program was interrupted, go to clean up
if (!isInterrupted) { if (!isInterrupted) {
this.addDiagnostics(await lintAst({ ast: ast })) this.addDiagnostics(await lintAst({ ast: ast }))
sceneInfra.modelingSend({ type: 'code edit during sketch' })
setSelectionFilterToDefault(execState.memory, this.engineCommandManager) setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
if (args.zoomToFit) { if (args.zoomToFit) {
@ -356,13 +358,7 @@ export class KclManager {
this.lastSuccessfulProgramMemory = execState.memory this.lastSuccessfulProgramMemory = execState.memory
} }
this.ast = { ...ast } this.ast = { ...ast }
// updateArtifactGraph relies on updated executeState/programMemory
await this.engineCommandManager.updateArtifactGraph(this.ast)
this._executeCallback() this._executeCallback()
if (!isInterrupted) {
sceneInfra.modelingSend({ type: 'code edit during sketch' })
}
this.engineCommandManager.addCommandLog({ this.engineCommandManager.addCommandLog({
type: 'execution-done', type: 'execution-done',
data: null, data: null,

View File

@ -66,7 +66,9 @@ export async function executeAst({
? enginelessExecutor(ast, programMemoryOverride) ? enginelessExecutor(ast, programMemoryOverride)
: _executor(ast, engineCommandManager)) : _executor(ast, engineCommandManager))
await engineCommandManager.waitForAllCommands() await engineCommandManager.waitForAllCommands(
programMemoryOverride !== undefined
)
return { return {
logs: [], logs: [],

View File

@ -45,7 +45,6 @@ import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { ExtrudeFacePlane } from 'machines/modelingMachine' import { ExtrudeFacePlane } from 'machines/modelingMachine'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { KclExpressionWithVariable } from 'lib/commandTypes'
export function startSketchOnDefault( export function startSketchOnDefault(
node: Node<Program>, node: Node<Program>,
@ -591,25 +590,6 @@ export function addOffsetPlane({
} }
} }
/**
* Return a modified clone of an AST with a named constant inserted into the body
*/
export function insertNamedConstant({
node,
newExpression,
}: {
node: Node<Program>
newExpression: KclExpressionWithVariable
}): Node<Program> {
const ast = structuredClone(node)
ast.body.splice(
newExpression.insertIndex,
0,
newExpression.variableDeclarationAst
)
return ast
}
/** /**
* Modify the AST to create a new sketch using the variable declaration * Modify the AST to create a new sketch using the variable declaration
* of an offset plane. The new sketch just has to come after the offset * of an offset plane. The new sketch just has to come after the offset
@ -953,31 +933,6 @@ export function giveSketchFnCallTag(
} }
} }
/**
* Replace a
*/
export function replaceValueAtNodePath({
ast,
pathToNode,
newExpressionString,
}: {
ast: Node<Program>
pathToNode: PathToNode
newExpressionString: string
}) {
const replaceCheckResult = isNodeSafeToReplacePath(ast, pathToNode)
if (err(replaceCheckResult)) {
return replaceCheckResult
}
const { isSafe, value, replacer } = replaceCheckResult
if (!isSafe || value.type === 'Identifier') {
return new Error('Not safe to replace')
}
return replacer(ast, newExpressionString)
}
export function moveValueIntoNewVariablePath( export function moveValueIntoNewVariablePath(
ast: Node<Program>, ast: Node<Program>,
programMemory: ProgramMemory, programMemory: ProgramMemory,

View File

@ -40,6 +40,7 @@ beforeAll(async () => {
makeDefaultPlanes: () => makeDefaultPlanes(engineCommandManager), makeDefaultPlanes: () => makeDefaultPlanes(engineCommandManager),
setMediaStream: () => {}, setMediaStream: () => {},
setIsStreamReady: () => {}, setIsStreamReady: () => {},
modifyGrid: async () => {},
callbackOnEngineLiteConnect: () => { callbackOnEngineLiteConnect: () => {
resolve(true) resolve(true)
}, },
@ -247,7 +248,7 @@ extrude003 = extrude(-15, sketch003)`
selectedSegmentSnippet, selectedSegmentSnippet,
expectedExtrudeSnippet expectedExtrudeSnippet
) )
}, 5_000) })
}) })
const runModifyAstCloneWithEdgeTreatmentAndTag = async ( const runModifyAstCloneWithEdgeTreatmentAndTag = async (
@ -477,7 +478,7 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> chamfer({ length = 5, tags = [seg01] }, %)` |> chamfer({ length: 5, tags: [seg01] }, %)`
const segmentSnippets = ['line([-20, 0], %)'] const segmentSnippets = ['line([-20, 0], %)']
const expectedCode = `sketch001 = startSketchOn('XY') const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
@ -487,8 +488,8 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> chamfer({ length = 5, tags = [seg01] }, %) |> chamfer({ length: 5, tags: [seg01] }, %)
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)` |> ${edgeTreatmentType}({ ${parameterName}: 3, tags: [seg02] }, %)`
await runModifyAstCloneWithEdgeTreatmentAndTag( await runModifyAstCloneWithEdgeTreatmentAndTag(
code, code,

View File

@ -335,7 +335,7 @@ export function mutateAstWithTagForSketchSegment(
return { modifiedAst: astClone, tag } return { modifiedAst: astClone, tag }
} }
export function getEdgeTagCall( function getEdgeTagCall(
tag: string, tag: string,
artifact: Artifact artifact: Artifact
): Node<Identifier | CallExpression> { ): Node<Identifier | CallExpression> {

View File

@ -1,154 +0,0 @@
import { err } from 'lib/trap'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
import {
Program,
PathToNode,
Expr,
CallExpression,
PipeExpression,
VariableDeclarator,
} from 'lang/wasm'
import { Selections } from 'lib/selections'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import {
createLiteral,
createCallExpressionStdLib,
createObjectExpression,
createIdentifier,
createPipeExpression,
findUniqueName,
createVariableDeclaration,
} from 'lang/modifyAst'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import {
mutateAstWithTagForSketchSegment,
getEdgeTagCall,
} from 'lang/modifyAst/addEdgeTreatment'
export function revolveSketch(
ast: Node<Program>,
pathToSketchNode: PathToNode,
shouldPipe = false,
angle: Expr = createLiteral(4),
axis: Selections
):
| {
modifiedAst: Node<Program>
pathToSketchNode: PathToNode
pathToRevolveArg: PathToNode
}
| Error {
const clonedAst = structuredClone(ast)
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
if (err(sketchNode)) return sketchNode
// testing code
const pathToAxisSelection = getNodePathFromSourceRange(
clonedAst,
axis.graphSelections[0]?.codeRef.range
)
const lineNode = getNodeFromPath<CallExpression>(
clonedAst,
pathToAxisSelection,
'CallExpression'
)
if (err(lineNode)) return lineNode
// TODO Kevin: What if |> close(%)?
// TODO Kevin: What if opposite edge
// TODO Kevin: What if the edge isn't planar to the sketch?
// TODO Kevin: add a tag.
const tagResult = mutateAstWithTagForSketchSegment(
clonedAst,
pathToAxisSelection
)
// Have the tag whether it is already created or a new one is generated
if (err(tagResult)) return tagResult
const { tag } = tagResult
/* Original Code */
const { node: sketchExpression } = sketchNode
// determine if sketchExpression is in a pipeExpression or not
const sketchPipeExpressionNode = getNodeFromPath<PipeExpression>(
clonedAst,
pathToSketchNode,
'PipeExpression'
)
if (err(sketchPipeExpressionNode)) return sketchPipeExpressionNode
const { node: sketchPipeExpression } = sketchPipeExpressionNode
const isInPipeExpression = sketchPipeExpression.type === 'PipeExpression'
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
clonedAst,
pathToSketchNode,
'VariableDeclarator'
)
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
const {
node: sketchVariableDeclarator,
shallowPath: sketchPathToDecleration,
} = sketchVariableDeclaratorNode
const axisSelection = axis?.graphSelections[0]?.artifact
if (!axisSelection) return new Error('Axis selection is missing.')
const revolveCall = createCallExpressionStdLib('revolve', [
createObjectExpression({
angle: angle,
axis: getEdgeTagCall(tag, axisSelection),
}),
createIdentifier(sketchVariableDeclarator.id.name),
])
if (shouldPipe) {
const pipeChain = createPipeExpression(
isInPipeExpression
? [...sketchPipeExpression.body, revolveCall]
: [sketchExpression as any, revolveCall]
)
sketchVariableDeclarator.init = pipeChain
const pathToRevolveArg: PathToNode = [
...sketchPathToDecleration,
['init', 'VariableDeclarator'],
['body', ''],
[pipeChain.body.length - 1, 'index'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst: clonedAst,
pathToSketchNode,
pathToRevolveArg,
}
}
// We're not creating a pipe expression,
// but rather a separate constant for the extrusion
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
const sketchIndexInPathToNode =
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
if (typeof sketchIndexInBody !== 'number')
return new Error('expected sketchIndexInBody to be a number')
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
const pathToRevolveArg: PathToNode = [
['body', ''],
[sketchIndexInBody + 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst: clonedAst,
pathToSketchNode: [...pathToSketchNode.slice(0, -1), [-1, 'index']],
pathToRevolveArg,
}
}

View File

@ -111,7 +111,8 @@ export function addShell({
const pathToNode: PathToNode = [ const pathToNode: PathToNode = [
['body', ''], ['body', ''],
[modifiedAst.body.length - 1, 'index'], [modifiedAst.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpression'], ['arguments', 'CallExpression'],
[0, 'index'], [0, 'index'],

View File

@ -661,7 +661,7 @@ describe('Testing doesSceneHaveExtrudedSketch', () => {
|> circle({ center = [0, 0], radius = 1 }, %) |> circle({ center = [0, 0], radius = 1 }, %)
extrude001 = extrude(1, sketch001) extrude001 = extrude(1, sketch001)
` `
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const extrudable = doesSceneHaveExtrudedSketch(ast) const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeTruthy() expect(extrudable).toBeTruthy()
@ -671,7 +671,7 @@ extrude001 = extrude(1, sketch001)
|> circle({ center = [0, 0], radius = 1 }, %) |> circle({ center = [0, 0], radius = 1 }, %)
|> extrude(1, %) |> extrude(1, %)
` `
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const extrudable = doesSceneHaveExtrudedSketch(ast) const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeTruthy() expect(extrudable).toBeTruthy()
@ -680,7 +680,7 @@ extrude001 = extrude(1, sketch001)
const exampleCode = `extrude001 = startSketchOn('XZ') const exampleCode = `extrude001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %) |> circle({ center = [0, 0], radius = 1 }, %)
` `
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const extrudable = doesSceneHaveExtrudedSketch(ast) const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeFalsy() expect(extrudable).toBeFalsy()

View File

@ -13,7 +13,7 @@ Map {
"range": [ "range": [
12, 12,
31, 31,
true, 0,
], ],
}, },
"id": "UUID", "id": "UUID",
@ -33,7 +33,7 @@ Map {
"range": [ "range": [
37, 37,
64, 64,
true, 0,
], ],
}, },
"id": "UUID", "id": "UUID",
@ -60,7 +60,7 @@ Map {
"range": [ "range": [
70, 70,
86, 86,
true, 0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -83,7 +83,7 @@ Map {
"range": [ "range": [
92, 92,
119, 119,
true, 0,
], ],
}, },
"edgeCutId": "UUID", "edgeCutId": "UUID",
@ -107,7 +107,7 @@ Map {
"range": [ "range": [
125, 125,
150, 150,
true, 0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -130,7 +130,7 @@ Map {
"range": [ "range": [
156, 156,
203, 203,
true, 0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -153,7 +153,7 @@ Map {
"range": [ "range": [
209, 209,
217, 217,
true, 0,
], ],
}, },
"edgeIds": [], "edgeIds": [],
@ -177,7 +177,7 @@ Map {
"range": [ "range": [
231, 231,
254, 254,
true, 0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -320,7 +320,7 @@ Map {
"range": [ "range": [
260, 260,
299, 299,
true, 0,
], ],
}, },
"consumedEdgeId": "UUID", "consumedEdgeId": "UUID",
@ -340,7 +340,7 @@ Map {
"range": [ "range": [
350, 350,
377, 377,
true, 0,
], ],
}, },
"id": "UUID", "id": "UUID",
@ -366,7 +366,7 @@ Map {
"range": [ "range": [
383, 383,
398, 398,
true, 0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -389,7 +389,7 @@ Map {
"range": [ "range": [
404, 404,
420, 420,
true, 0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -412,7 +412,7 @@ Map {
"range": [ "range": [
426, 426,
473, 473,
true, 0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -435,7 +435,7 @@ Map {
"range": [ "range": [
479, 479,
487, 487,
true, 0,
], ],
}, },
"edgeIds": [], "edgeIds": [],
@ -459,7 +459,7 @@ Map {
"range": [ "range": [
501, 501,
522, 522,
true, 0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -478,6 +478,7 @@ Map {
"UUID", "UUID",
"UUID", "UUID",
"UUID", "UUID",
"UUID",
], ],
"type": "sweep", "type": "sweep",
}, },
@ -509,46 +510,54 @@ Map {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"id": "UUID", "id": "UUID",
"pathIds": [], "pathIds": [],
"subType": "end", "subType": "start",
"sweepId": "UUID", "sweepId": "UUID",
"type": "cap", "type": "cap",
}, },
"UUID-35" => { "UUID-35" => {
"edgeCutEdgeIds": [],
"id": "UUID", "id": "UUID",
"segId": "UUID", "pathIds": [],
"subType": "opposite", "subType": "end",
"sweepId": "UUID", "sweepId": "UUID",
"type": "sweepEdge", "type": "cap",
}, },
"UUID-36" => { "UUID-36" => {
"id": "UUID", "id": "UUID",
"segId": "UUID", "segId": "UUID",
"subType": "adjacent", "subType": "opposite",
"sweepId": "UUID", "sweepId": "UUID",
"type": "sweepEdge", "type": "sweepEdge",
}, },
"UUID-37" => { "UUID-37" => {
"id": "UUID", "id": "UUID",
"segId": "UUID", "segId": "UUID",
"subType": "opposite", "subType": "adjacent",
"sweepId": "UUID", "sweepId": "UUID",
"type": "sweepEdge", "type": "sweepEdge",
}, },
"UUID-38" => { "UUID-38" => {
"id": "UUID", "id": "UUID",
"segId": "UUID", "segId": "UUID",
"subType": "adjacent", "subType": "opposite",
"sweepId": "UUID", "sweepId": "UUID",
"type": "sweepEdge", "type": "sweepEdge",
}, },
"UUID-39" => { "UUID-39" => {
"id": "UUID", "id": "UUID",
"segId": "UUID", "segId": "UUID",
"subType": "opposite", "subType": "adjacent",
"sweepId": "UUID", "sweepId": "UUID",
"type": "sweepEdge", "type": "sweepEdge",
}, },
"UUID-40" => { "UUID-40" => {
"id": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-41" => {
"id": "UUID", "id": "UUID",
"segId": "UUID", "segId": "UUID",
"subType": "adjacent", "subType": "adjacent",

View File

@ -139,6 +139,7 @@ beforeAll(async () => {
makeDefaultPlanes: () => makeDefaultPlanes(engineCommandManager), makeDefaultPlanes: () => makeDefaultPlanes(engineCommandManager),
setMediaStream: () => {}, setMediaStream: () => {},
setIsStreamReady: () => {}, setIsStreamReady: () => {},
modifyGrid: async () => {},
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
callbackOnEngineLiteConnect: async () => { callbackOnEngineLiteConnect: async () => {
const cacheEntries = Object.entries(codeToWriteCacheFor) as [ const cacheEntries = Object.entries(codeToWriteCacheFor) as [
@ -259,13 +260,11 @@ describe('testing createArtifactGraph', () => {
if (err(extrusion)) throw extrusion if (err(extrusion)) throw extrusion
expect(extrusion.type).toBe('sweep') expect(extrusion.type).toBe('sweep')
const firstExtrusionIsACubeIE6Sides = 6 const firstExtrusionIsACubeIE6Sides = 6
// Each face of the triangular prism (5), but without the bottom cap. const secondExtrusionIsATriangularPrismIE5Sides = 5
// The engine doesn't generate that.
const secondExtrusionIsATriangularPrism = 4
expect(extrusion.surfaces.length).toBe( expect(extrusion.surfaces.length).toBe(
!index !index
? firstExtrusionIsACubeIE6Sides ? firstExtrusionIsACubeIE6Sides
: secondExtrusionIsATriangularPrism : secondExtrusionIsATriangularPrismIE5Sides
) )
}) })
}) })
@ -661,7 +660,7 @@ describe('testing getArtifactsToUpdate', () => {
sweepId: '', sweepId: '',
codeRef: { codeRef: {
pathToNode: [['body', '']], pathToNode: [['body', '']],
range: [37, 64, true], range: [37, 64, 0],
}, },
}, },
]) ])
@ -674,7 +673,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: [], surfaceIds: [],
edgeIds: [], edgeIds: [],
codeRef: { codeRef: {
range: [231, 254, true], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -685,7 +684,7 @@ describe('testing getArtifactsToUpdate', () => {
planeId: expect.any(String), planeId: expect.any(String),
sweepId: expect.any(String), sweepId: expect.any(String),
codeRef: { codeRef: {
range: [37, 64, true], range: [37, 64, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
solid2dId: expect.any(String), solid2dId: expect.any(String),
@ -699,7 +698,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: '', surfaceId: '',
edgeIds: [], edgeIds: [],
codeRef: { codeRef: {
range: [70, 86, true], range: [70, 86, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -710,7 +709,7 @@ describe('testing getArtifactsToUpdate', () => {
planeId: expect.any(String), planeId: expect.any(String),
sweepId: expect.any(String), sweepId: expect.any(String),
codeRef: { codeRef: {
range: [37, 64, true], range: [37, 64, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
solid2dId: expect.any(String), solid2dId: expect.any(String),
@ -725,7 +724,7 @@ describe('testing getArtifactsToUpdate', () => {
edgeIds: [], edgeIds: [],
surfaceId: '', surfaceId: '',
codeRef: { codeRef: {
range: [260, 299, true], range: [260, 299, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -736,7 +735,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [92, 119, true], range: [92, 119, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
edgeCutId: expect.any(String), edgeCutId: expect.any(String),
@ -758,7 +757,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [156, 203, true], range: [156, 203, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -770,7 +769,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254, true], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -789,7 +788,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [125, 150, true], range: [125, 150, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -801,7 +800,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254, true], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -820,7 +819,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [92, 119, true], range: [92, 119, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
edgeCutId: expect.any(String), edgeCutId: expect.any(String),
@ -833,7 +832,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254, true], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -852,7 +851,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [70, 86, true], range: [70, 86, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -864,7 +863,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254, true], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -884,7 +883,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254, true], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -904,7 +903,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254, true], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 568 KiB

After

Width:  |  Height:  |  Size: 577 KiB

View File

@ -1,11 +1,4 @@
import { import { defaultSourceRange, SourceRange } from 'lang/wasm'
defaultRustSourceRange,
defaultSourceRange,
Program,
RustSourceRange,
SourceRange,
sourceRangeFromRust,
} from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env' import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave' import { exportSave } from 'lib/exportSave'
@ -1309,8 +1302,8 @@ export enum EngineCommandManagerEvents {
interface PendingMessage { interface PendingMessage {
command: EngineCommand command: EngineCommand
range: RustSourceRange range: SourceRange
idToRangeMap: { [key: string]: RustSourceRange } idToRangeMap: { [key: string]: SourceRange }
resolve: (data: [Models['WebSocketResponse_type']]) => void resolve: (data: [Models['WebSocketResponse_type']]) => void
reject: (reason: string) => void reject: (reason: string) => void
promise: Promise<[Models['WebSocketResponse_type']]> promise: Promise<[Models['WebSocketResponse_type']]>
@ -1406,6 +1399,7 @@ export class EngineCommandManager extends EventTarget {
} }
private makeDefaultPlanes: () => Promise<DefaultPlanes> | null = () => null private makeDefaultPlanes: () => Promise<DefaultPlanes> | null = () => null
private modifyGrid: (hidden: boolean) => Promise<void> | null = () => null
private onEngineConnectionOpened = () => {} private onEngineConnectionOpened = () => {}
private onEngineConnectionClosed = () => {} private onEngineConnectionClosed = () => {}
@ -1438,6 +1432,7 @@ export class EngineCommandManager extends EventTarget {
height, height,
token, token,
makeDefaultPlanes, makeDefaultPlanes,
modifyGrid,
settings = { settings = {
pool: null, pool: null,
theme: Themes.Dark, theme: Themes.Dark,
@ -1457,12 +1452,14 @@ export class EngineCommandManager extends EventTarget {
height: number height: number
token?: string token?: string
makeDefaultPlanes: () => Promise<DefaultPlanes> makeDefaultPlanes: () => Promise<DefaultPlanes>
modifyGrid: (hidden: boolean) => Promise<void>
settings?: SettingsViaQueryString settings?: SettingsViaQueryString
}) { }) {
if (settings) { if (settings) {
this.settings = settings this.settings = settings
} }
this.makeDefaultPlanes = makeDefaultPlanes this.makeDefaultPlanes = makeDefaultPlanes
this.modifyGrid = modifyGrid
if (width === 0 || height === 0) { if (width === 0 || height === 0) {
return return
} }
@ -1542,15 +1539,21 @@ export class EngineCommandManager extends EventTarget {
type: 'default_camera_get_settings', type: 'default_camera_get_settings',
}, },
}) })
await this.initPlanes() // We want modify the grid first because we don't want it to flash.
setIsStreamReady(true) // Ideally these would already be default hidden in engine (TODO do
// that) https://github.com/KittyCAD/engine/issues/2282
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.modifyGrid(!this.settings.showScaleGrid)?.then(async () => {
await this.initPlanes()
setIsStreamReady(true)
// Other parts of the application should use this to react on scene ready. // Other parts of the application should use this to react on scene ready.
this.dispatchEvent( this.dispatchEvent(
new CustomEvent(EngineCommandManagerEvents.SceneReady, { new CustomEvent(EngineCommandManagerEvents.SceneReady, {
detail: this.engineConnection, detail: this.engineConnection,
}) })
) )
})
} }
this.engineConnection.addEventListener( this.engineConnection.addEventListener(
@ -2000,7 +2003,7 @@ export class EngineCommandManager extends EventTarget {
{ {
command, command,
idToRangeMap: {}, idToRangeMap: {},
range: defaultRustSourceRange(), range: defaultSourceRange(),
}, },
true // isSceneCommand true // isSceneCommand
) )
@ -2031,9 +2034,9 @@ export class EngineCommandManager extends EventTarget {
return Promise.reject(new Error('rangeStr is undefined')) return Promise.reject(new Error('rangeStr is undefined'))
if (commandStr === undefined) if (commandStr === undefined)
return Promise.reject(new Error('commandStr is undefined')) return Promise.reject(new Error('commandStr is undefined'))
const range: RustSourceRange = JSON.parse(rangeStr) const range: SourceRange = JSON.parse(rangeStr)
const command: EngineCommand = JSON.parse(commandStr) const command: EngineCommand = JSON.parse(commandStr)
const idToRangeMap: { [key: string]: RustSourceRange } = const idToRangeMap: { [key: string]: SourceRange } =
JSON.parse(idToRangeStr) JSON.parse(idToRangeStr)
// Current executeAst is stale, going to interrupt, a new executeAst will trigger // Current executeAst is stale, going to interrupt, a new executeAst will trigger
@ -2076,14 +2079,10 @@ export class EngineCommandManager extends EventTarget {
if (message.command.type === 'modeling_cmd_req') { if (message.command.type === 'modeling_cmd_req') {
this.orderedCommands.push({ this.orderedCommands.push({
command: message.command, command: message.command,
range: sourceRangeFromRust(message.range), range: message.range,
}) })
} else if (message.command.type === 'modeling_cmd_batch_req') { } else if (message.command.type === 'modeling_cmd_batch_req') {
message.command.requests.forEach((req) => { message.command.requests.forEach((req) => {
const cmdId = req.cmd_id || ''
const range = cmdId
? sourceRangeFromRust(message.idToRangeMap[cmdId])
: defaultSourceRange()
const cmd: EngineCommand = { const cmd: EngineCommand = {
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: req.cmd_id, cmd_id: req.cmd_id,
@ -2091,7 +2090,7 @@ export class EngineCommandManager extends EventTarget {
} }
this.orderedCommands.push({ this.orderedCommands.push({
command: cmd, command: cmd,
range, range: message.idToRangeMap[req.cmd_id || ''],
}) })
}) })
} }
@ -2110,23 +2109,30 @@ export class EngineCommandManager extends EventTarget {
* When an execution takes place we want to wait until we've got replies for all of the commands * When an execution takes place we want to wait until we've got replies for all of the commands
* When this is done when we build the artifact map synchronously. * When this is done when we build the artifact map synchronously.
*/ */
waitForAllCommands() { async waitForAllCommands(useFakeExecutor = false) {
return Promise.all( await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise))
Object.values(this.pendingCommands).map((a) => a.promise) setTimeout(() => {
) // the ast is wrong without this one tick timeout.
} // an example is `Solids should be select and deletable` e2e test will fail
updateArtifactGraph(ast: Program) { // because the out of date ast messes with selections
this.artifactGraph = createArtifactGraph({ // TODO: race condition
orderedCommands: this.orderedCommands, if (!this?.kclManager) return
responseMap: this.responseMap, this.artifactGraph = createArtifactGraph({
ast, orderedCommands: this.orderedCommands,
responseMap: this.responseMap,
ast: this.kclManager.ast,
})
if (useFakeExecutor) {
// mock executions don't produce an artifactGraph, so this will always be empty
// skipping the below logic to wait for the next real execution
return
}
if (this.artifactGraph.size) {
this.deferredArtifactEmptied(null)
} else {
this.deferredArtifactPopulated(null)
}
}) })
// TODO check if these still need to be deferred once e2e tests are working again.
if (this.artifactGraph.size) {
this.deferredArtifactEmptied(null)
} else {
this.deferredArtifactPopulated(null)
}
} }
/** /**
@ -2206,6 +2212,15 @@ export class EngineCommandManager extends EventTarget {
}).catch(reportRejection) }).catch(reportRejection)
} }
/**
* Set the visibility of the scale grid in the engine scene.
* @param visible - whether to show or hide the scale grid
*/
setScaleGridVisibility(visible: boolean) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.modifyGrid(!visible)
}
// Some "objects" have the same source range, such as sketch_mode_start and start_path. // Some "objects" have the same source range, such as sketch_mode_start and start_path.
// So when passing a range, we need to also specify the command type // So when passing a range, we need to also specify the command type
mapRangeToObjectId( mapRangeToObjectId(

View File

@ -1,13 +1,9 @@
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { initPromise, parse, ParseResult } from './wasm' import { parse, ParseResult } from './wasm'
import { enginelessExecutor } from 'lib/testHelpers' import { enginelessExecutor } from 'lib/testHelpers'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Program } from '../wasm-lib/kcl/bindings/Program' import { Program } from '../wasm-lib/kcl/bindings/Program'
beforeEach(async () => {
await initPromise
})
it('can execute parsed AST', async () => { it('can execute parsed AST', async () => {
const code = `x = 1 const code = `x = 1
// A comment.` // A comment.`

View File

@ -1,13 +1,14 @@
import init, { import init, {
parse_wasm, parse_wasm,
recast_wasm, recast_wasm,
execute, execute_wasm,
kcl_lint, kcl_lint,
modify_ast_for_sketch_wasm, modify_ast_for_sketch_wasm,
is_points_ccw, is_points_ccw,
get_tangential_arc_to_info, get_tangential_arc_to_info,
program_memory_init, program_memory_init,
make_default_planes, make_default_planes,
modify_grid,
coredump, coredump,
toml_stringify, toml_stringify,
default_app_settings, default_app_settings,
@ -42,9 +43,7 @@ import { Environment } from '../wasm-lib/kcl/bindings/Environment'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError' import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange' import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
import { getAllCurrentSettings } from 'lib/settings/settingsUtils'
export type { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
export type { Program } from '../wasm-lib/kcl/bindings/Program' export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr' export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
export type { ObjectExpression } from '../wasm-lib/kcl/bindings/ObjectExpression' export type { ObjectExpression } from '../wasm-lib/kcl/bindings/ObjectExpression'
@ -65,7 +64,6 @@ export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
export type { Literal } from '../wasm-lib/kcl/bindings/Literal' export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue' export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression' export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
export type { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
export type SyntaxType = export type SyntaxType =
| 'Program' | 'Program'
@ -94,37 +92,16 @@ export type { Solid } from '../wasm-lib/kcl/bindings/Solid'
export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue' export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface' export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
/**
* The first two items are the start and end points (byte offsets from the start of the file).
* The third item is whether the source range belongs to the 'main' file, i.e., the file currently
* being rendered/displayed in the editor (TODO we need to handle modules better in the frontend).
*/
export type SourceRange = [number, number, boolean] export type SourceRange = [number, number, boolean]
/**
* Convert a SourceRange as used inside the KCL interpreter into the above one for use in the
* frontend (essentially we're eagerly checking whether the frontend should care about the SourceRange
* so as not to expose details of the interpreter's current representation of module ids throughout
* the frontend).
*/
export function sourceRangeFromRust(s: RustSourceRange): SourceRange { export function sourceRangeFromRust(s: RustSourceRange): SourceRange {
return [s[0], s[1], s[2] === 0] return [s[0], s[1], s[2] === 0]
} }
/**
* Create a default SourceRange for testing or as a placeholder.
*/
export function defaultSourceRange(): SourceRange { export function defaultSourceRange(): SourceRange {
return [0, 0, true] return [0, 0, true]
} }
/**
* Create a default RustSourceRange for testing or as a placeholder.
*/
export function defaultRustSourceRange(): RustSourceRange {
return [0, 0, 0]
}
export const wasmUrl = () => { export const wasmUrl = () => {
// For when we're in electron (file based) or web server (network based) // For when we're in electron (file based) or web server (network based)
// For some reason relative paths don't work as expected. Otherwise we would // For some reason relative paths don't work as expected. Otherwise we would
@ -145,7 +122,7 @@ const initialise = async () => {
const fullUrl = wasmUrl() const fullUrl = wasmUrl()
const input = await fetch(fullUrl) const input = await fetch(fullUrl)
const buffer = await input.arrayBuffer() const buffer = await input.arrayBuffer()
return await init({ module_or_path: buffer }) return await init(buffer)
} catch (e) { } catch (e) {
console.log('Error initialising WASM', e) console.log('Error initialising WASM', e)
return Promise.reject(e) return Promise.reject(e)
@ -186,10 +163,6 @@ export class ParseResult {
} }
} }
/**
* Parsing was successful. There is guaranteed to be an AST and no fatal errors. There may or may
* not be warnings or non-fatal errors.
*/
class SuccessParseResult extends ParseResult { class SuccessParseResult extends ParseResult {
program: Node<Program> program: Node<Program>
@ -520,19 +493,18 @@ export const _executor = async (
return Promise.reject(programMemoryOverride) return Promise.reject(programMemoryOverride)
try { try {
let jsAppSettings = default_app_settings() let baseUnit = 'mm'
if (!TEST) { if (!TEST) {
const lastSettingsSnapshot = await import( const getSettingsState = import('components/SettingsAuthProvider').then(
'components/SettingsAuthProvider' (module) => module.getSettingsState
).then((module) => module.lastSettingsContextSnapshot) )
if (lastSettingsSnapshot) { baseUnit =
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot) (await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
}
} }
const execState: RawExecState = await execute( const execState: RawExecState = await execute_wasm(
JSON.stringify(node), JSON.stringify(node),
JSON.stringify(programMemoryOverride?.toRaw() || null), JSON.stringify(programMemoryOverride?.toRaw() || null),
JSON.stringify({ settings: jsAppSettings }), baseUnit,
engineCommandManager, engineCommandManager,
fileSystemManager fileSystemManager
) )
@ -580,6 +552,20 @@ export const makeDefaultPlanes = async (
} }
} }
export const modifyGrid = async (
engineCommandManager: EngineCommandManager,
hidden: boolean
): Promise<void> => {
try {
await modify_grid(engineCommandManager, hidden)
return
} catch (e) {
// TODO: do something real with the error.
console.log('modify grid error', e)
return Promise.reject(e)
}
}
export const modifyAstForSketch = async ( export const modifyAstForSketch = async (
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
ast: Node<Program>, ast: Node<Program>,

View File

@ -10,7 +10,7 @@ const noModifiersPressed = (e: MouseEvent) =>
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
export type CameraSystem = export type CameraSystem =
| 'Zoo' | 'KittyCAD'
| 'OnShape' | 'OnShape'
| 'Trackpad Friendly' | 'Trackpad Friendly'
| 'Solidworks' | 'Solidworks'
@ -19,7 +19,7 @@ export type CameraSystem =
| 'AutoCAD' | 'AutoCAD'
export const cameraSystems: CameraSystem[] = [ export const cameraSystems: CameraSystem[] = [
'Zoo', 'KittyCAD',
'OnShape', 'OnShape',
'Trackpad Friendly', 'Trackpad Friendly',
'Solidworks', 'Solidworks',
@ -32,13 +32,8 @@ export function mouseControlsToCameraSystem(
mouseControl: MouseControlType | undefined mouseControl: MouseControlType | undefined
): CameraSystem | undefined { ): CameraSystem | undefined {
switch (mouseControl) { switch (mouseControl) {
// TODO: understand why the values come back without underscores and fix the root cause case 'kitty_cad':
// @ts-ignore: TS2678 return 'KittyCAD'
case 'zoo':
return 'Zoo'
// TODO: understand why the values come back without underscores and fix the root cause
// @ts-ignore: TS2678
case 'onshape':
case 'on_shape': case 'on_shape':
return 'OnShape' return 'OnShape'
case 'trackpad_friendly': case 'trackpad_friendly':
@ -49,9 +44,6 @@ export function mouseControlsToCameraSystem(
return 'NX' return 'NX'
case 'creo': case 'creo':
return 'Creo' return 'Creo'
// TODO: understand why the values come back without underscores and fix the root cause
// @ts-ignore: TS2678
case 'autocad':
case 'auto_cad': case 'auto_cad':
return 'AutoCAD' return 'AutoCAD'
default: default:
@ -85,7 +77,7 @@ export const btnName = (e: MouseEvent) => ({
}) })
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = { export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
Zoo: { KittyCAD: {
pan: { pan: {
description: 'Shift + Right click drag or middle click drag', description: 'Shift + Right click drag or middle click drag',
callback: (e) => callback: (e) =>

View File

@ -1,15 +1,9 @@
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { angleLengthInfo } from 'components/Toolbar/setAngleLength'
import { transformAstSketchLines } from 'lang/std/sketchcombos'
import { PathToNode } from 'lang/wasm'
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes' import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants' import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
import { components } from 'lib/machine-api' import { components } from 'lib/machine-api'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap'
import { modelingMachine, SketchTool } from 'machines/modelingMachine' import { modelingMachine, SketchTool } from 'machines/modelingMachine'
import { revolveAxisValidator } from './validators'
type OutputFormat = Models['OutputFormat_type'] type OutputFormat = Models['OutputFormat_type']
type OutputTypeKey = OutputFormat['type'] type OutputTypeKey = OutputFormat['type']
@ -47,7 +41,6 @@ export type ModelingCommandSchema = {
Revolve: { Revolve: {
selection: Selections selection: Selections
angle: KclCommandValue angle: KclCommandValue
axis: Selections
} }
Fillet: { Fillet: {
// todo // todo
@ -61,18 +54,6 @@ export type ModelingCommandSchema = {
'change tool': { 'change tool': {
tool: SketchTool tool: SketchTool
} }
'Constrain length': {
selection: Selections
length: KclCommandValue
}
'Constrain with named value': {
currentValue: {
valueText: string
pathToNode: PathToNode
variableName: string
}
namedValue: KclCommandValue
}
'Text-to-CAD': { 'Text-to-CAD': {
prompt: string prompt: string
} }
@ -332,13 +313,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
required: true, required: true,
skip: true, skip: true,
}, },
axis: {
required: true,
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: false,
validation: revolveAxisValidator,
},
angle: { angle: {
inputType: 'kcl', inputType: 'kcl',
defaultValue: KCL_DEFAULT_DEGREE, defaultValue: KCL_DEFAULT_DEGREE,
@ -386,88 +360,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
}, },
}, },
}, },
'Constrain length': {
description: 'Constrain the length of one or more segments.',
icon: 'dimension',
args: {
selection: {
inputType: 'selection',
selectionTypes: ['segment'],
multiple: false,
required: true,
skip: true,
},
length: {
inputType: 'kcl',
required: true,
createVariableByDefault: true,
defaultValue(_, machineContext) {
const selectionRanges = machineContext?.selectionRanges
if (!selectionRanges) return KCL_DEFAULT_LENGTH
const angleLength = angleLengthInfo({
selectionRanges,
angleOrLength: 'setLength',
})
if (err(angleLength)) return KCL_DEFAULT_LENGTH
const { transforms } = angleLength
// QUESTION: is it okay to reference kclManager here? will its state be up to date?
const sketched = transformAstSketchLines({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
if (err(sketched)) return KCL_DEFAULT_LENGTH
const { valueUsedInTransform } = sketched
return valueUsedInTransform?.toString() || KCL_DEFAULT_LENGTH
},
},
},
},
'Constrain with named value': {
description: 'Constrain a value by making it a named constant.',
icon: 'make-variable',
args: {
currentValue: {
description:
'Path to the node in the AST to constrain. This is never shown to the user.',
inputType: 'text',
required: false,
skip: true,
},
namedValue: {
inputType: 'kcl',
required: true,
createVariableByDefault: true,
variableName(commandBarContext, machineContext) {
const { currentValue } = commandBarContext.argumentsToSubmit
if (
!currentValue ||
!(currentValue instanceof Object) ||
!('variableName' in currentValue) ||
typeof currentValue.variableName !== 'string'
) {
return 'value'
}
return currentValue.variableName
},
defaultValue: (commandBarContext) => {
const { currentValue } = commandBarContext.argumentsToSubmit
if (
!currentValue ||
!(currentValue instanceof Object) ||
!('valueText' in currentValue) ||
typeof currentValue.valueText !== 'string'
) {
return KCL_DEFAULT_LENGTH
}
return currentValue.valueText
},
},
},
},
'Text-to-CAD': { 'Text-to-CAD': {
description: 'Use the Zoo Text-to-CAD API to generate part starters.', description: 'Use the Zoo Text-to-CAD API to generate part starters.',
icon: 'chat', icon: 'chat',

View File

@ -1,106 +0,0 @@
import { Models } from '@kittycad/lib'
import { engineCommandManager } from 'lib/singletons'
import { uuidv4 } from 'lib/utils'
import { CommandBarContext } from 'machines/commandBarMachine'
import { Selections } from 'lib/selections'
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
for (let tries = 0; tries < numberOfRetries; tries++) {
try {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'disable_dry_run' },
})
// Exit out since the command was successful
return
} catch (e) {
console.error(e)
console.error('disable_dry_run failed. This is bad!')
}
}
}
// Takes a callback function and wraps it around enable_dry_run and disable_dry_run
export const dryRunWrapper = async (callback: () => Promise<any>) => {
// Gotcha: What about race conditions?
try {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'enable_dry_run' },
})
const result = await callback()
return result
} catch (e) {
console.error(e)
} finally {
await disableDryRunWithRetry(5)
}
}
function isSelections(selections: unknown): selections is Selections {
return (
(selections as Selections).graphSelections !== undefined &&
(selections as Selections).otherSelections !== undefined
)
}
export const revolveAxisValidator = async ({
data,
context,
}: {
data: { [key: string]: Selections }
context: CommandBarContext
}): Promise<boolean | string> => {
if (!isSelections(context.argumentsToSubmit.selection)) {
return 'Unable to revolve, selections are missing'
}
const artifact =
context.argumentsToSubmit.selection.graphSelections[0].artifact
if (!artifact) {
return 'Unable to revolve, sketch not found'
}
if (!('pathId' in artifact)) {
return 'Unable to revolve, sketch has no path'
}
const sketchSelection = artifact.pathId
let edgeSelection = data.axis.graphSelections[0].artifact?.id
if (!sketchSelection) {
return 'Unable to revolve, sketch is missing'
}
if (!edgeSelection) {
return 'Unable to revolve, edge is missing'
}
const angleInDegrees: Models['Angle_type'] = {
unit: 'degrees',
value: 360,
}
const revolveAboutEdgeCommand = async () => {
return await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'revolve_about_edge',
angle: angleInDegrees,
edge_id: edgeSelection,
target: sketchSelection,
tolerance: 0.0001,
},
})
}
const attemptRevolve = await dryRunWrapper(revolveAboutEdgeCommand)
if (attemptRevolve?.success) {
return true
} else {
// return error message for the toast
return 'Unable to revolve with selected axis'
}
}

View File

@ -7,7 +7,7 @@ import { ReactNode } from 'react'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Artifact } from 'lang/std/artifactGraph' import { Artifact } from 'lang/std/artifactGraph'
import { CommandBarContext } from 'machines/commandBarMachine'
type Icon = CustomIconName type Icon = CustomIconName
const PLATFORMS = ['both', 'web', 'desktop'] as const const PLATFORMS = ['both', 'web', 'desktop'] as const
const INPUT_TYPES = [ const INPUT_TYPES = [
@ -147,30 +147,8 @@ export type CommandArgumentConfig<
inputType: 'selection' inputType: 'selection'
selectionTypes: Artifact['type'][] selectionTypes: Artifact['type'][]
multiple: boolean multiple: boolean
validation?: ({
data,
context,
}: {
data: any
context: CommandBarContext
}) => Promise<boolean | string>
}
| {
inputType: 'kcl'
createVariableByDefault?: boolean
variableName?:
| string
| ((
commandBarContext: ContextFrom<typeof commandBarMachine>,
machineContext?: C
) => string)
defaultValue?:
| string
| ((
commandBarContext: ContextFrom<typeof commandBarMachine>,
machineContext?: C
) => string)
} }
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default values
| { | {
inputType: 'string' inputType: 'string'
defaultValue?: defaultValue?:
@ -243,30 +221,8 @@ export type CommandArgument<
inputType: 'selection' inputType: 'selection'
selectionTypes: Artifact['type'][] selectionTypes: Artifact['type'][]
multiple: boolean multiple: boolean
validation?: ({
data,
context,
}: {
data: any
context: CommandBarContext
}) => Promise<boolean | string>
}
| {
inputType: 'kcl'
createVariableByDefault?: boolean
variableName?:
| string
| ((
commandBarContext: ContextFrom<typeof commandBarMachine>,
machineContext?: ContextFrom<T>
) => string)
defaultValue?:
| string
| ((
commandBarContext: ContextFrom<typeof commandBarMachine>,
machineContext?: ContextFrom<T>
) => string)
} }
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default value
| { | {
inputType: 'string' inputType: 'string'
defaultValue?: defaultValue?:

View File

@ -111,28 +111,3 @@ export const KCL_SAMPLES_MANIFEST_URLS = {
/** Toast id for the app auto-updater toast */ /** Toast id for the app auto-updater toast */
export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast' export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast'
/** Local sketch axis values in KCL for operations, it could either be 'X' or 'Y' */
export const KCL_AXIS_X = 'X'
export const KCL_AXIS_Y = 'Y'
export const KCL_AXIS_NEG_X = '-X'
export const KCL_AXIS_NEG_Y = '-Y'
export const KCL_DEFAULT_AXIS = 'X'
export enum AxisNames {
X = 'x',
Y = 'y',
Z = 'z',
NEG_X = '-x',
NEG_Y = '-y',
NEG_Z = '-z',
}
/** Semantic names of views from AxisNames */
export const VIEW_NAMES_SEMANTIC = {
[AxisNames.X]: 'Right',
[AxisNames.Y]: 'Back',
[AxisNames.Z]: 'Top',
[AxisNames.NEG_X]: 'Left',
[AxisNames.NEG_Y]: 'Front',
[AxisNames.NEG_Z]: 'Bottom',
} as const

View File

@ -155,8 +155,6 @@ export function buildCommandArgument<
context: ContextFrom<T>, context: ContextFrom<T>,
machineActor: Actor<T> machineActor: Actor<T>
): CommandArgument<O, T> & { inputType: typeof arg.inputType } { ): CommandArgument<O, T> & { inputType: typeof arg.inputType } {
// GOTCHA: modelingCommandConfig is not a 1:1 mapping to this baseCommandArgument
// You need to manually add key/value pairs here.
const baseCommandArgument = { const baseCommandArgument = {
description: arg.description, description: arg.description,
required: arg.required, required: arg.required,
@ -183,13 +181,10 @@ export function buildCommandArgument<
...baseCommandArgument, ...baseCommandArgument,
multiple: arg.multiple, multiple: arg.multiple,
selectionTypes: arg.selectionTypes, selectionTypes: arg.selectionTypes,
validation: arg.validation,
} satisfies CommandArgument<O, T> & { inputType: 'selection' } } satisfies CommandArgument<O, T> & { inputType: 'selection' }
} else if (arg.inputType === 'kcl') { } else if (arg.inputType === 'kcl') {
return { return {
inputType: arg.inputType, inputType: arg.inputType,
createVariableByDefault: arg.createVariableByDefault,
variableName: arg.variableName,
defaultValue: arg.defaultValue, defaultValue: arg.defaultValue,
...baseCommandArgument, ...baseCommandArgument,
} satisfies CommandArgument<O, T> & { inputType: 'kcl' } } satisfies CommandArgument<O, T> & { inputType: 'kcl' }

View File

@ -15,7 +15,6 @@ import { fileSystemManager } from 'lang/std/fileSystemManager'
import { getProjectInfo } from './desktop' import { getProjectInfo } from './desktop'
import { createSettings } from './settings/initialSettings' import { createSettings } from './settings/initialSettings'
import { normalizeLineEndings } from 'lib/codeEditor' import { normalizeLineEndings } from 'lib/codeEditor'
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
// The root loader simply resolves the settings and any errors that // The root loader simply resolves the settings and any errors that
// occurred during the settings load // occurred during the settings load
@ -54,15 +53,14 @@ export const telemetryLoader: LoaderFunction = async ({
// Redirect users to the appropriate onboarding page if they haven't completed it // Redirect users to the appropriate onboarding page if they haven't completed it
export const onboardingRedirectLoader: ActionFunction = async (args) => { export const onboardingRedirectLoader: ActionFunction = async (args) => {
const { settings } = await loadAndValidateSettings() const { settings } = await loadAndValidateSettings()
const onboardingStatus: OnboardingStatus = const onboardingStatus = settings.app.onboardingStatus.current || ''
settings.app.onboardingStatus.current || ''
const notEnRouteToOnboarding = !args.request.url.includes( const notEnRouteToOnboarding = !args.request.url.includes(
PATHS.ONBOARDING.INDEX PATHS.ONBOARDING.INDEX
) )
// '' is the initial state, 'completed' and 'dismissed' are the final states // '' is the initial state, 'done' and 'dismissed' are the final states
const hasValidOnboardingStatus = const hasValidOnboardingStatus =
onboardingStatus.length === 0 || onboardingStatus.length === 0 ||
!(onboardingStatus === 'completed' || onboardingStatus === 'dismissed') !(onboardingStatus === 'done' || onboardingStatus === 'dismissed')
const shouldRedirectToOnboarding = const shouldRedirectToOnboarding =
notEnRouteToOnboarding && hasValidOnboardingStatus notEnRouteToOnboarding && hasValidOnboardingStatus

View File

@ -569,17 +569,6 @@ export function canSweepSelection(selection: Selections) {
) )
} }
export function canRevolveSelection(selection: Selections) {
const commonNodes = selection.graphSelections.map((_, i) =>
buildCommonNodeFromSelection(selection, i)
)
return (
!!isSketchPipe(selection) &&
(commonNodes.every((n) => nodeHasClose(n)) ||
commonNodes.every((n) => nodeHasCircle(n)))
)
}
export function canLoftSelection(selection: Selections) { export function canLoftSelection(selection: Selections) {
const commonNodes = selection.graphSelections.map((_, i) => const commonNodes = selection.graphSelections.map((_, i) =>
buildCommonNodeFromSelection(selection, i) buildCommonNodeFromSelection(selection, i)
@ -641,29 +630,12 @@ export function getSelectionCountByType(
} }
}) })
selection.graphSelections.forEach((graphSelection) => { selection.graphSelections.forEach((selection) => {
if (!graphSelection.artifact) { if (!selection.artifact) {
/** incrementOrInitializeSelectionType('other')
* TODO: remove this heuristic-based selection type detection. return
* Currently, if you've created a sketch and have not left sketch mode,
* the selection will be a segment selection with no artifact.
* This is because the mock execution does not update the artifact graph.
* Once we move the artifactGraph creation to WASM, we can remove this,
* as the artifactGraph will always be up-to-date.
*/
if (isSingleCursorInPipe(selection, kclManager.ast)) {
incrementOrInitializeSelectionType('segment')
return
} else {
console.warn(
'Selection is outside of a sketch but has no artifact. Sketch segment selections are the only kind that can have a valid selection with no artifact.',
JSON.stringify(graphSelection)
)
incrementOrInitializeSelectionType('other')
return
}
} }
incrementOrInitializeSelectionType(graphSelection.artifact.type) incrementOrInitializeSelectionType(selection.artifact.type)
}) })
return selectionsByType return selectionsByType

View File

@ -12,7 +12,7 @@ export type InteractionMapItem = {
* Controls both the available names for interaction map categories * Controls both the available names for interaction map categories
* and the order in which they are displayed. * and the order in which they are displayed.
*/ */
const interactionMapCategories = [ export const interactionMapCategories = [
'Sketching', 'Sketching',
'Modeling', 'Modeling',
'Command Palette', 'Command Palette',

View File

@ -19,7 +19,6 @@ import Tooltip from 'components/Tooltip'
import { toSync } from 'lib/utils' import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType' import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
/** /**
* A setting that can be set at the user or project level * A setting that can be set at the user or project level
@ -190,10 +189,8 @@ export function createSettings() {
inputType: 'boolean', inputType: 'boolean',
}, },
}), }),
onboardingStatus: new Setting<OnboardingStatus>({ onboardingStatus: new Setting<string>({
defaultValue: '', defaultValue: '',
// TODO: this could be better but we don't have a TS side real enum
// for this yet
validate: (v) => typeof v === 'string', validate: (v) => typeof v === 'string',
hideOnPlatform: 'both', hideOnPlatform: 'both',
}), }),
@ -286,7 +283,7 @@ export function createSettings() {
* The controls for how to navigate the 3D view * The controls for how to navigate the 3D view
*/ */
mouseControls: new Setting<CameraSystem>({ mouseControls: new Setting<CameraSystem>({
defaultValue: 'Zoo', defaultValue: 'KittyCAD',
description: 'The controls for how to navigate the 3D view', description: 'The controls for how to navigate the 3D view',
validate: (v) => cameraSystems.includes(v as CameraSystem), validate: (v) => cameraSystems.includes(v as CameraSystem),
hideOnLevel: 'project', hideOnLevel: 'project',

View File

@ -2,7 +2,6 @@ import { DeepPartial } from 'lib/types'
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration' import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { import {
configurationToSettingsPayload, configurationToSettingsPayload,
getAllCurrentSettings,
projectConfigurationToSettingsPayload, projectConfigurationToSettingsPayload,
setSettingsAtLevel, setSettingsAtLevel,
} from './settingsUtils' } from './settingsUtils'
@ -66,48 +65,3 @@ describe(`testing settings initialization`, () => {
expect(settings.app.themeColor.current).toBe('200') expect(settings.app.themeColor.current).toBe('200')
}) })
}) })
describe(`testing getAllCurrentSettings`, () => {
it(`returns the correct settings`, () => {
// Set up the settings
let settings = createSettings()
const appConfiguration: DeepPartial<Configuration> = {
settings: {
app: {
appearance: {
theme: 'dark',
color: 190,
},
},
},
}
const projectConfiguration: DeepPartial<Configuration> = {
settings: {
app: {
appearance: {
theme: 'light',
color: 200,
},
},
modeling: {
base_unit: 'ft',
},
},
}
const appSettingsPayload = configurationToSettingsPayload(appConfiguration)
const projectSettingsPayload =
projectConfigurationToSettingsPayload(projectConfiguration)
setSettingsAtLevel(settings, 'user', appSettingsPayload)
setSettingsAtLevel(settings, 'project', projectSettingsPayload)
// Now the test: get all the settings' current resolved values
const allCurrentSettings = getAllCurrentSettings(settings)
// This one gets the 'user'-level theme because it's ignored at the project level
// (see the test "doesn't read theme from project settings")
expect(allCurrentSettings.app.theme).toBe('dark')
expect(allCurrentSettings.app.themeColor).toBe('200')
expect(allCurrentSettings.modeling.defaultUnit).toBe('ft')
})
})

View File

@ -286,27 +286,6 @@ export function getChangedSettingsAtLevel(
return changedSettings return changedSettings
} }
export function getAllCurrentSettings(
allSettings: typeof settings
): SaveSettingsPayload {
const currentSettings = {} as SaveSettingsPayload
Object.entries(allSettings).forEach(([category, settingsCategory]) => {
const categoryKey = category as keyof typeof settings
Object.entries(settingsCategory).forEach(
([setting, settingValue]: [string, Setting]) => {
const settingKey =
setting as keyof (typeof settings)[typeof categoryKey]
currentSettings[categoryKey] = {
...currentSettings[categoryKey],
[settingKey]: settingValue.current,
}
}
)
})
return currentSettings
}
export function setSettingsAtLevel( export function setSettingsAtLevel(
allSettings: typeof settings, allSettings: typeof settings,
level: SettingsLevel, level: SettingsLevel,

View File

@ -112,6 +112,9 @@ export async function executor(
makeDefaultPlanes: () => { makeDefaultPlanes: () => {
return new Promise((resolve) => resolve(defaultPlanes)) return new Promise((resolve) => resolve(defaultPlanes))
}, },
modifyGrid: (hidden: boolean) => {
return new Promise((resolve) => resolve())
},
}) })
return new Promise((resolve) => { return new Promise((resolve) => {

View File

@ -540,15 +540,13 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
[ [
{ {
id: 'constraint-length', id: 'constraint-length',
disabled: (state) => !state.matches({ Sketch: 'SketchIdle' }), disabled: (state) =>
onClick: ({ commandBarSend }) => !(
commandBarSend({ state.matches({ Sketch: 'SketchIdle' }) &&
type: 'Find and select command', state.can({ type: 'Constrain length' })
data: { ),
name: 'Constrain length', onClick: ({ modelingSend }) =>
groupId: 'modeling', modelingSend({ type: 'Constrain length' }),
},
}),
icon: 'dimension', icon: 'dimension',
status: 'available', status: 'available',
title: 'Length', title: 'Length',

View File

@ -8,7 +8,6 @@ import {
import { Selections__old } from 'lib/selections' import { Selections__old } from 'lib/selections'
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils' import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import toast from 'react-hot-toast'
export type CommandBarContext = { export type CommandBarContext = {
commands: Command[] commands: Command[]
@ -248,69 +247,14 @@ export const commandBarMachine = setup({
'All arguments are skippable': () => false, 'All arguments are skippable': () => false,
}, },
actors: { actors: {
'Validate argument': fromPromise( 'Validate argument': fromPromise(({ input }) => {
({ return new Promise((resolve, reject) => {
input, // TODO: figure out if we should validate argument data here or in the form itself,
}: { // and if we should support people configuring a argument's validation function
input: {
context: CommandBarContext | undefined
event: CommandBarMachineEvent | undefined
}
}) => {
return new Promise((resolve, reject) => {
if (!input || input?.event?.type !== 'Submit argument') {
toast.error(`Unable to validate, wrong event type.`)
return reject(`Unable to validate, wrong event type`)
}
const context = input?.context resolve(input)
})
if (!context) { }),
toast.error(`Unable to validate, wrong argument.`)
return reject(`Unable to validate, wrong argument`)
}
const data = input.event.data
const argName = context.currentArgument?.name
const args = context?.selectedCommand?.args
const argConfig = args && argName ? args[argName] : undefined
// Only do a validation check if the argument, selectedCommand, and the validation function are defined
if (
context.currentArgument &&
context.selectedCommand &&
argConfig?.inputType === 'selection' &&
argConfig?.validation
) {
argConfig
.validation({ context, data })
.then((result) => {
if (typeof result === 'boolean' && result === true) {
return resolve(data)
} else {
// validation failed
if (typeof result === 'string') {
// The result of the validation is the error message
toast.error(result)
return reject(
`unable to validate ${argName}, Message: ${result}`
)
} else {
// Default message if there is not a custom one sent
toast.error(`Unable to validate ${argName}`)
return reject(`unable to validate ${argName}}`)
}
}
})
.catch(() => {
return reject(`unable to validate ${argName}}`)
})
} else {
// Missing several requirements for validate argument, just bypass
return resolve(data)
}
})
}
),
'Validate all arguments': fromPromise( 'Validate all arguments': fromPromise(
({ input }: { input: CommandBarContext }) => { ({ input }: { input: CommandBarContext }) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -505,10 +449,9 @@ export const commandBarMachine = setup({
invoke: { invoke: {
src: 'Validate argument', src: 'Validate argument',
id: 'validateSingleArgument', id: 'validateSingleArgument',
input: ({ event, context }) => { input: ({ event }) => {
if (event.type !== 'Submit argument') if (event.type !== 'Submit argument') return {}
return { event: undefined, context: undefined } return event.data
return { event, context }
}, },
onDone: { onDone: {
target: '#Command Bar.Checking Arguments', target: '#Command Bar.Checking Arguments',

File diff suppressed because one or more lines are too long

View File

@ -42,6 +42,8 @@ export const settingsMachine = setup({
setClientTheme: () => {}, setClientTheme: () => {},
'Execute AST': () => {}, 'Execute AST': () => {},
toastSuccess: () => {}, toastSuccess: () => {},
setEngineEdges: () => {},
setEngineScaleGridVisibility: () => {},
setClientSideSceneUnits: () => {}, setClientSideSceneUnits: () => {},
persistSettings: () => {}, persistSettings: () => {},
resetSettings: assign(({ context, event }) => { resetSettings: assign(({ context, event }) => {
@ -170,7 +172,7 @@ export const settingsMachine = setup({
'set.modeling.highlightEdges': { 'set.modeling.highlightEdges': {
target: 'persisting settings', target: 'persisting settings',
actions: ['setSettingAtLevel', 'toastSuccess', 'Execute AST'], actions: ['setSettingAtLevel', 'toastSuccess', 'setEngineEdges'],
}, },
'Reset settings': { 'Reset settings': {
@ -199,7 +201,11 @@ export const settingsMachine = setup({
'set.modeling.showScaleGrid': { 'set.modeling.showScaleGrid': {
target: 'persisting settings', target: 'persisting settings',
actions: ['setSettingAtLevel', 'toastSuccess', 'Execute AST'], actions: [
'setSettingAtLevel',
'toastSuccess',
'setEngineScaleGridVisibility',
],
}, },
}, },
}, },

View File

@ -44,6 +44,11 @@ process.env.VITE_KC_SITE_BASE_URL ??= 'https://zoo.dev'
process.env.VITE_KC_SKIP_AUTH ??= 'false' process.env.VITE_KC_SKIP_AUTH ??= 'false'
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??= '15000' process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??= '15000'
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit()
}
const ZOO_STUDIO_PROTOCOL = 'zoo-studio' const ZOO_STUDIO_PROTOCOL = 'zoo-studio'
/// Register our application to handle all "electron-fiddle://" protocols. /// Register our application to handle all "electron-fiddle://" protocols.
@ -251,9 +256,6 @@ export function getAutoUpdater(): AppUpdater {
app.on('ready', () => { app.on('ready', () => {
const autoUpdater = getAutoUpdater() const autoUpdater = getAutoUpdater()
// TODO: we're getting `Error: Response ends without calling any handlers` with our setup,
// so at the moment this isn't worth enabling
autoUpdater.disableDifferentialDownload = true
setTimeout(() => { setTimeout(() => {
autoUpdater.checkForUpdates().catch(reportRejection) autoUpdater.checkForUpdates().catch(reportRejection)
}, 1000) }, 1000)

View File

@ -1,6 +1,4 @@
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus' export const onboardingPaths = {
export const onboardingPaths: Record<string, OnboardingStatus> = {
INDEX: '/', INDEX: '/',
CAMERA: '/camera', CAMERA: '/camera',
STREAMING: '/streaming', STREAMING: '/streaming',

View File

@ -30,14 +30,6 @@ export const PACKAGE_NAME = isDesktop()
? window.electron.packageJson.name ? window.electron.packageJson.name
: 'zoo-modeling-app' : 'zoo-modeling-app'
export const IS_NIGHTLY = PACKAGE_NAME.indexOf('-nightly') > -1
export function getReleaseUrl(version: string = APP_VERSION) {
return `https://github.com/KittyCAD/modeling-app/releases/tag/${
IS_NIGHTLY ? 'nightly-' : ''
}v${version}`
}
export const Settings = () => { export const Settings = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams() const [searchParams, setSearchParams] = useSearchParams()

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

@ -121,9 +121,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.94" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
dependencies = [ dependencies = [
"backtrace", "backtrace",
] ]
@ -401,9 +401,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.39" version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@ -723,7 +723,7 @@ dependencies = [
[[package]] [[package]]
name = "derive-docs" name = "derive-docs"
version = "0.1.33" version = "0.1.32"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"anyhow", "anyhow",
@ -1112,7 +1112,7 @@ dependencies = [
"fnv", "fnv",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http 1.2.0", "http 1.1.0",
"indexmap 2.7.0", "indexmap 2.7.0",
"slab", "slab",
"tokio", "tokio",
@ -1215,9 +1215,9 @@ dependencies = [
[[package]] [[package]]
name = "http" name = "http"
version = "1.2.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -1242,7 +1242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [ dependencies = [
"bytes", "bytes",
"http 1.2.0", "http 1.1.0",
] ]
[[package]] [[package]]
@ -1253,7 +1253,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.2.0", "http 1.1.0",
"http-body 1.0.1", "http-body 1.0.1",
"pin-project-lite", "pin-project-lite",
] ]
@ -1303,7 +1303,7 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"h2", "h2",
"http 1.2.0", "http 1.1.0",
"http-body 1.0.1", "http-body 1.0.1",
"httparse", "httparse",
"itoa", "itoa",
@ -1320,7 +1320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http 1.2.0", "http 1.1.0",
"hyper 1.4.1", "hyper 1.4.1",
"hyper-util", "hyper-util",
"rustls", "rustls",
@ -1340,7 +1340,7 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"http 1.2.0", "http 1.1.0",
"http-body 1.0.1", "http-body 1.0.1",
"hyper 1.4.1", "hyper 1.4.1",
"pin-project-lite", "pin-project-lite",
@ -1674,17 +1674,16 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.76" version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
dependencies = [ dependencies = [
"once_cell",
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.2.29" version = "0.2.28"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1706,7 +1705,7 @@ dependencies = [
"git_rev", "git_rev",
"gltf-json", "gltf-json",
"handlebars", "handlebars",
"http 1.2.0", "http 1.1.0",
"iai", "iai",
"image", "image",
"indexmap 2.7.0", "indexmap 2.7.0",
@ -1722,9 +1721,7 @@ dependencies = [
"parse-display 0.9.1", "parse-display 0.9.1",
"pretty_assertions", "pretty_assertions",
"pyo3", "pyo3",
"regex",
"reqwest", "reqwest",
"rgba_simple",
"ropey", "ropey",
"schemars", "schemars",
"serde", "serde",
@ -1752,7 +1749,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.19" version = "0.1.18"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.30", "hyper 0.14.30",
@ -1793,7 +1790,7 @@ dependencies = [
"data-encoding", "data-encoding",
"format_serde_error", "format_serde_error",
"futures", "futures",
"http 1.2.0", "http 1.1.0",
"itertools 0.13.0", "itertools 0.13.0",
"log", "log",
"mime_guess", "mime_guess",
@ -1819,9 +1816,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.2.79" version = "0.2.77"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a9cab4476455be70ea57643c31444068b056d091bd348cab6044c0d8ad7fcc" checksum = "3b77259b37acafa360d98af27431ac394bc8899eeed7037513832ddbee856811"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1829,7 +1826,7 @@ dependencies = [
"enum-iterator", "enum-iterator",
"enum-iterator-derive", "enum-iterator-derive",
"euler", "euler",
"http 1.2.0", "http 1.1.0",
"kittycad-modeling-cmds-macros", "kittycad-modeling-cmds-macros",
"kittycad-unit-conversion-derive", "kittycad-unit-conversion-derive",
"measurements", "measurements",
@ -2863,7 +2860,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2", "h2",
"http 1.2.0", "http 1.1.0",
"http-body 1.0.1", "http-body 1.0.1",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper 1.4.1",
@ -2905,7 +2902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b" checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"http 1.2.0", "http 1.1.0",
"reqwest", "reqwest",
"reqwest-middleware", "reqwest-middleware",
] ]
@ -2918,7 +2915,7 @@ checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"http 1.2.0", "http 1.1.0",
"reqwest", "reqwest",
"serde", "serde",
"thiserror 1.0.68", "thiserror 1.0.68",
@ -2935,7 +2932,7 @@ dependencies = [
"async-trait", "async-trait",
"futures", "futures",
"getrandom", "getrandom",
"http 1.2.0", "http 1.1.0",
"hyper 1.4.1", "hyper 1.4.1",
"parking_lot 0.11.2", "parking_lot 0.11.2",
"reqwest", "reqwest",
@ -2956,7 +2953,7 @@ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"getrandom", "getrandom",
"http 1.2.0", "http 1.1.0",
"matchit", "matchit",
"opentelemetry", "opentelemetry",
"reqwest", "reqwest",
@ -2974,12 +2971,6 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
] ]
[[package]]
name = "rgba_simple"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6cd655523701785087f69900df39892fb7b9b0721aa67682f571c38c32ac58a"
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.8" version = "0.17.8"
@ -3200,9 +3191,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.216" version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -3218,9 +3209,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.216" version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4028,7 +4019,7 @@ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
"data-encoding", "data-encoding",
"http 1.2.0", "http 1.1.0",
"httparse", "httparse",
"log", "log",
"rand 0.8.5", "rand 0.8.5",
@ -4236,9 +4227,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@ -4247,12 +4238,13 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
"once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.87",
@ -4261,23 +4253,22 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.49" version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" checksum = "65471f79c1022ffa5291d33520cbbb53b7687b01c2f8e83b57d102eed7ed479d"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-core", "futures-core",
"js-sys", "js-sys",
"once_cell",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
] ]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -4285,9 +4276,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4298,9 +4289,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.99" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]] [[package]]
name = "wasm-lib" name = "wasm-lib"
@ -4362,9 +4353,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.76" version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",

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