Compare commits
42 Commits
import-fix
...
v0.26.0
Author | SHA1 | Date | |
---|---|---|---|
cb8fc33adb | |||
2dc8b429ff | |||
19ffa220e8 | |||
5332ddd88e | |||
11d9a2ee00 | |||
bfebc41a5c | |||
824b4c823e | |||
785002fa4e | |||
f650281855 | |||
9f6999829a | |||
a14bbaa237 | |||
0706624381 | |||
ef0ae5e06e | |||
a010743abb | |||
057ee479c3 | |||
7218efc489 | |||
b6dd6e7dd0 | |||
47af18f533 | |||
0505220dac | |||
f7711b71d6 | |||
0255fde5fe | |||
ebade29ed0 | |||
582d37e51b | |||
4ef9429842 | |||
0577b6a984 | |||
7d44de0c12 | |||
f7d5313588 | |||
bd4783e885 | |||
8794696b26 | |||
1c2e415c70 | |||
248ef8ebb3 | |||
fbac9935fe | |||
b4c171a347 | |||
0811d9fa4e | |||
1efc2b9762 | |||
d361bda180 | |||
1d3ade114f | |||
3382b66075 | |||
5e8b5c254d | |||
b99b2d9a96 | |||
81041661c7 | |||
9d99b5be7f |
@ -1,3 +1,3 @@
|
|||||||
[codespell]
|
[codespell]
|
||||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
|
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
|
||||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock
|
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts
|
||||||
|
95
.github/workflows/build-test-publish-apps.yml
vendored
@ -72,6 +72,18 @@ jobs:
|
|||||||
- id: export_version
|
- id: export_version
|
||||||
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Prepare electron-builder.yml file for nightly
|
||||||
|
if: ${{ github.event_name == 'schedule' }}
|
||||||
|
run: |
|
||||||
|
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/nightly"' electron-builder.yml
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: ${{ github.event_name == 'schedule' }}
|
||||||
|
with:
|
||||||
|
name: prepared-files-nightly
|
||||||
|
path: |
|
||||||
|
electron-builder.yml
|
||||||
|
|
||||||
- id: export_notes
|
- id: export_notes
|
||||||
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
|
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
@ -81,6 +93,7 @@ jobs:
|
|||||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test-release-notes"' electron-builder.yml
|
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test-release-notes"' electron-builder.yml
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||||
with:
|
with:
|
||||||
name: prepared-files-updater-test
|
name: prepared-files-updater-test
|
||||||
path: |
|
path: |
|
||||||
@ -92,7 +105,13 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-14, windows-2022, ubuntu-22.04]
|
include:
|
||||||
|
- os: macos-14
|
||||||
|
platform: mac
|
||||||
|
- os: windows-2022
|
||||||
|
platform: win
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
platform: linux
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
env:
|
env:
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
@ -121,6 +140,16 @@ jobs:
|
|||||||
cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
|
cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
|
||||||
cp prepared-files/release-notes.md release-notes.md
|
cp prepared-files/release-notes.md release-notes.md
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
if: ${{ github.event_name == 'schedule' }}
|
||||||
|
name: prepared-files-nightly
|
||||||
|
|
||||||
|
- name: Copy updated electron-builder.yml file for nightly build
|
||||||
|
if: ${{ github.event_name == 'schedule' }}
|
||||||
|
run: |
|
||||||
|
ls -R prepared-files-nightly
|
||||||
|
cp prepared-files-nightly/electron-builder.yml electron-builder.yml
|
||||||
|
|
||||||
- name: Sync node version and setup cache
|
- name: Sync node version and setup cache
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@ -165,9 +194,27 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: out-${{ matrix.os }}
|
name: out-arm64-${{ matrix.platform }}
|
||||||
|
# first two will pick both Zoo Modeling App-$VERSION-arm64-win.exe and Zoo Modeling App-$VERSION-win.exe
|
||||||
|
path: |
|
||||||
|
out/*-${{ env.VERSION_NO_V }}-win.*
|
||||||
|
out/*-${{ env.VERSION_NO_V }}-arm64-win.*
|
||||||
|
out/*-arm64-mac.*
|
||||||
|
out/*-arm64-linux.*
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: out-x64-${{ matrix.platform }}
|
||||||
|
path: |
|
||||||
|
out/*-x64-win.*
|
||||||
|
out/*-x64-mac.*
|
||||||
|
out/*-x86_64-linux.*
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||||
|
with:
|
||||||
|
name: out-yml
|
||||||
path: |
|
path: |
|
||||||
out/Zoo*.*
|
|
||||||
out/latest*.yml
|
out/latest*.yml
|
||||||
|
|
||||||
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
|
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
|
||||||
@ -189,10 +236,20 @@ jobs:
|
|||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||||
with:
|
with:
|
||||||
name: updater-test-${{ matrix.os }}
|
name: updater-test-arm64-${{ matrix.platform }}
|
||||||
path: |
|
path: |
|
||||||
out/Zoo*.*
|
out/*-arm64-win.exe
|
||||||
out/latest*.yml
|
out/*-arm64-mac.dmg
|
||||||
|
out/*-arm64-linux.AppImage
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||||
|
with:
|
||||||
|
name: updater-test-x64-${{ matrix.platform }}
|
||||||
|
path: |
|
||||||
|
out/*-x64-win.exe
|
||||||
|
out/*-x64-mac.dmg
|
||||||
|
out/*-x86_64-linux.AppImage
|
||||||
|
|
||||||
|
|
||||||
publish-apps-release:
|
publish-apps-release:
|
||||||
@ -214,17 +271,37 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: out-windows-2022
|
name: out-arm64-win
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: out-macos-14
|
name: out-x64-win
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: out-ubuntu-22.04
|
name: out-arm64-mac
|
||||||
|
path: out
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: out-x64-mac
|
||||||
|
path: out
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: out-arm64-linux
|
||||||
|
path: out
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: out-x64-linux
|
||||||
|
path: out
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: out-yml
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Generate the download static endpoint
|
- name: Generate the download static endpoint
|
||||||
|
13
.github/workflows/static-analysis.yml
vendored
@ -37,10 +37,6 @@ jobs:
|
|||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: './src/wasm-lib'
|
|
||||||
|
|
||||||
- run: yarn build:wasm
|
- run: yarn build:wasm
|
||||||
|
|
||||||
yarn-tsc:
|
yarn-tsc:
|
||||||
@ -70,10 +66,6 @@ jobs:
|
|||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: './src/wasm-lib'
|
|
||||||
|
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
|
||||||
python-codespell:
|
python-codespell:
|
||||||
@ -101,11 +93,6 @@ jobs:
|
|||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
|
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: './src/wasm-lib'
|
|
||||||
|
|
||||||
- run: yarn build:wasm
|
- run: yarn build:wasm
|
||||||
|
|
||||||
- run: yarn simpleserver:bg
|
- run: yarn simpleserver:bg
|
||||||
|
22
README.md
@ -158,11 +158,29 @@ The PR may then serve as a place to discuss the human-readable changelog and ext
|
|||||||
|
|
||||||
#### 3. Manually test artifacts from the Cut Release PR
|
#### 3. Manually test artifacts from the Cut Release PR
|
||||||
|
|
||||||
The release builds can be find under the `artifact` zip, at the very bottom of the `ci` action page for each commit on this branch.
|
##### Release builds
|
||||||
|
|
||||||
|
The release builds can be found under the `out-{platform}` zip, at the very bottom of the `build-publish-apps` summary page for each commit on this branch.
|
||||||
|
|
||||||
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the Cut Release PR.
|
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the Cut Release PR.
|
||||||
|
|
||||||
The other `ci` output in Cut Release PRs is `updater-test`, because we don't have a way to test this fully automated, we have a semi-automated process. Download updater-test zip file, install the app, run it, expect an updater prompt to a dummy v0.99.99, install it and check that the app comes back at that version (on both macOS and Windows).
|
##### Updater-test builds
|
||||||
|
|
||||||
|
The other `build-publish-apps` output in Cut Release PRs is `updater-test-{platform}`. As we don't have a way to test this fully automatically, we have a semi-automated process. For macOS, Windows, and Linux, download the corresponding updater-test artifact file, install the app, run it, expect an updater prompt to a dummy v0.255.255, install it and check that the app comes back at that version.
|
||||||
|
|
||||||
|
The only difference with these builds is that they point to a different update location on the release bucket, with this dummy v0.255.255 always available. This helps ensuring that the version we release will be able to update to the next one available.
|
||||||
|
|
||||||
|
If the prompt doesn't show up, start the app in command line to grab the electron-updater logs. This is likely an issue with the current build that needs addressing (or the updater-test location in the storage bucket).
|
||||||
|
```
|
||||||
|
# Windows (PowerShell)
|
||||||
|
& 'C:\Program Files\Zoo Modeling App\Zoo Modeling App.exe'
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
/Applications/Zoo\ Modeling\ App.app/Contents/MacOS/Zoo\ Modeling\ App
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
./Zoo Modeling App-{version}-{arch}-linux.AppImage
|
||||||
|
```
|
||||||
|
|
||||||
#### 4. Merge the Cut Release PR
|
#### 4. Merge the Cut Release PR
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ exampleSketch = startSketchOn('XZ')
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> patternCircular2d({
|
|> patternCircular2d({
|
||||||
center: [0, 0],
|
center: [0, 0],
|
||||||
repetitions: 12,
|
instances: 13,
|
||||||
arcDegrees: 360,
|
arcDegrees: 360,
|
||||||
rotateDuplicates: true
|
rotateDuplicates: true
|
||||||
}, %)
|
}, %)
|
||||||
|
@ -35,7 +35,7 @@ example = extrude(-5, exampleSketch)
|
|||||||
|> patternCircular3d({
|
|> patternCircular3d({
|
||||||
axis: [1, -1, 0],
|
axis: [1, -1, 0],
|
||||||
center: [10, -20, 0],
|
center: [10, -20, 0],
|
||||||
repetitions: 10,
|
instances: 11,
|
||||||
arcDegrees: 360,
|
arcDegrees: 360,
|
||||||
rotateDuplicates: true
|
rotateDuplicates: true
|
||||||
}, %)
|
}, %)
|
||||||
|
@ -32,7 +32,7 @@ exampleSketch = startSketchOn('XZ')
|
|||||||
|> circle({ center: [0, 0], radius: 1 }, %)
|
|> circle({ center: [0, 0], radius: 1 }, %)
|
||||||
|> patternLinear2d({
|
|> patternLinear2d({
|
||||||
axis: [1, 0],
|
axis: [1, 0],
|
||||||
repetitions: 6,
|
instances: 7,
|
||||||
distance: 4
|
distance: 4
|
||||||
}, %)
|
}, %)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ exampleSketch = startSketchOn('XZ')
|
|||||||
example = extrude(1, exampleSketch)
|
example = extrude(1, exampleSketch)
|
||||||
|> patternLinear3d({
|
|> patternLinear3d({
|
||||||
axis: [1, 0, 1],
|
axis: [1, 0, 1],
|
||||||
repetitions: 6,
|
instances: 7,
|
||||||
distance: 6
|
distance: 6
|
||||||
}, %)
|
}, %)
|
||||||
```
|
```
|
||||||
|
@ -32,7 +32,7 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
|
|||||||
fn decagon = (radius) => {
|
fn decagon = (radius) => {
|
||||||
step = 1 / 10 * tau()
|
step = 1 / 10 * tau()
|
||||||
sketch001 = startSketchAt([cos(0) * radius, sin(0) * radius])
|
sketch001 = startSketchAt([cos(0) * radius, sin(0) * radius])
|
||||||
return reduce([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], sketch001, (i, sg) => {
|
return reduce([1..10], sketch001, (i, sg) => {
|
||||||
x = cos(step * i) * radius
|
x = cos(step * i) * radius
|
||||||
y = sin(step * i) * radius
|
y = sin(step * i) * radius
|
||||||
return lineTo([x, y], sg)
|
return lineTo([x, y], sg)
|
||||||
|
1466
docs/kcl/std.json
@ -82,6 +82,78 @@ Raise a number to a power.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Are two numbers equal?
|
||||||
|
|
||||||
|
**enum:** `==`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Are two numbers not equal?
|
||||||
|
|
||||||
|
**enum:** `!=`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Is left greater than right
|
||||||
|
|
||||||
|
**enum:** `>`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Is left greater than or equal to right
|
||||||
|
|
||||||
|
**enum:** `>=`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Is left less than right
|
||||||
|
|
||||||
|
**enum:** `<`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Is left less than or equal to right
|
||||||
|
|
||||||
|
**enum:** `<=`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,6 +18,27 @@ layout: manual
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `ImportStatement`| | No |
|
||||||
|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||||
|
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||||
|
| `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
|
||||||
|
| `path` |`string`| | No |
|
||||||
|
| `raw_path` |`string`| | No |
|
||||||
|
| `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 |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
@ -45,6 +66,7 @@ layout: manual
|
|||||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||||
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | No |
|
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | No |
|
||||||
|
| `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)| | No |
|
||||||
| `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)| | No |
|
| `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)| | No |
|
||||||
| `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 |
|
| `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 |
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ Data for a circular pattern on a 2D sketch.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `repetitions` |[`Uint`](/docs/kcl/types/Uint)| The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. | No |
|
| `instances` |[`Uint`](/docs/kcl/types/Uint)| The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | No |
|
||||||
| `center` |`[number, number]`| The center about which to make the pattern. This is a 2D vector. | No |
|
| `center` |`[number, number]`| The center about which to make the pattern. This is a 2D vector. | No |
|
||||||
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
||||||
| `rotateDuplicates` |`boolean`| Whether or not to rotate the duplicates as they are copied. | No |
|
| `rotateDuplicates` |`boolean`| Whether or not to rotate the duplicates as they are copied. | No |
|
||||||
|
@ -16,7 +16,7 @@ Data for a circular pattern on a 3D model.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `repetitions` |[`Uint`](/docs/kcl/types/Uint)| The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. | No |
|
| `instances` |[`Uint`](/docs/kcl/types/Uint)| The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | No |
|
||||||
| `axis` |`[number, number, number]`| The axis around which to make the pattern. This is a 3D vector. | No |
|
| `axis` |`[number, number, number]`| The axis around which to make the pattern. This is a 3D vector. | No |
|
||||||
| `center` |`[number, number, number]`| The center about which to make the pattern. This is a 3D vector. | No |
|
| `center` |`[number, number, number]`| The center about which to make the pattern. This is a 3D vector. | No |
|
||||||
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
||||||
|
@ -197,6 +197,27 @@ An expression can be evaluated to yield a single KCL value.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `ArrayRangeExpression`| | No |
|
||||||
|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||||
|
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||||
|
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||||
|
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||||
|
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
|
||||||
|
| `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 |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|
24
docs/kcl/types/ImportItem.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: "ImportItem"
|
||||||
|
excerpt: ""
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No |
|
||||||
|
| `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No |
|
||||||
|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||||
|
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||||
|
| `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 |
|
||||||
|
|
||||||
|
|
16
docs/kcl/types/ItemVisibility.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: "ItemVisibility"
|
||||||
|
excerpt: ""
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
**enum:** `default`, `export`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ Data for a linear pattern on a 2D sketch.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `repetitions` |[`Uint`](/docs/kcl/types/Uint)| The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. | No |
|
| `instances` |[`Uint`](/docs/kcl/types/Uint)| The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | No |
|
||||||
| `distance` |`number`| The distance between each repetition. This can also be referred to as spacing. | No |
|
| `distance` |`number`| The distance between each repetition. This can also be referred to as spacing. | No |
|
||||||
| `axis` |`[number, number]`| The axis of the pattern. This is a 2D vector. | No |
|
| `axis` |`[number, number]`| The axis of the pattern. This is a 2D vector. | No |
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ Data for a linear pattern on a 3D model.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `repetitions` |[`Uint`](/docs/kcl/types/Uint)| The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. | No |
|
| `instances` |[`Uint`](/docs/kcl/types/Uint)| The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | No |
|
||||||
| `distance` |`number`| The distance between each repetition. This can also be referred to as spacing. | No |
|
| `distance` |`number`| The distance between each repetition. This can also be referred to as spacing. | No |
|
||||||
| `axis` |`[number, number, number]`| The axis of the pattern. | No |
|
| `axis` |`[number, number, number]`| The axis of the pattern. | No |
|
||||||
|
|
||||||
|
@ -313,3 +313,45 @@ test(
|
|||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'external change of file contents are reflected in editor',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const PROJECT_DIR_NAME = 'lee-was-here'
|
||||||
|
const {
|
||||||
|
electronApp,
|
||||||
|
page,
|
||||||
|
dir: projectsDir,
|
||||||
|
} = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
const aProjectDir = join(dir, PROJECT_DIR_NAME)
|
||||||
|
await fsp.mkdir(aProjectDir, { recursive: true })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await test.step('Open the project', async () => {
|
||||||
|
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
|
||||||
|
await page.getByText(PROJECT_DIR_NAME).click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
})
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
|
||||||
|
await test.step('Write to file externally and check for changed content', async () => {
|
||||||
|
const content = 'ha he ho ho ha blap scap be dap'
|
||||||
|
await fsp.writeFile(
|
||||||
|
join(projectsDir, PROJECT_DIR_NAME, 'main.kcl'),
|
||||||
|
content
|
||||||
|
)
|
||||||
|
await u.editorTextMatches(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -104,7 +104,7 @@ test(
|
|||||||
},
|
},
|
||||||
{ timeout: 15_000 }
|
{ timeout: 15_000 }
|
||||||
)
|
)
|
||||||
.toBe(431341)
|
.toBeGreaterThan(300_000)
|
||||||
|
|
||||||
// clean up output.gltf
|
// clean up output.gltf
|
||||||
await fsp.rm('output.gltf')
|
await fsp.rm('output.gltf')
|
||||||
@ -179,7 +179,7 @@ test(
|
|||||||
},
|
},
|
||||||
{ timeout: 15_000 }
|
{ timeout: 15_000 }
|
||||||
)
|
)
|
||||||
.toBe(102040)
|
.toBeGreaterThan(100_000)
|
||||||
|
|
||||||
// clean up output.gltf
|
// clean up output.gltf
|
||||||
await fsp.rm('output.gltf')
|
await fsp.rm('output.gltf')
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
|
import fsp from 'fs/promises'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
import {
|
||||||
|
darkModeBgColor,
|
||||||
|
darkModePlaneColorXZ,
|
||||||
|
executorInputPath,
|
||||||
|
getUtils,
|
||||||
|
setup,
|
||||||
|
setupElectron,
|
||||||
|
tearDown,
|
||||||
|
} from './test-utils'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||||
await setup(context, page, testInfo)
|
await setup(context, page, testInfo)
|
||||||
@ -974,4 +984,84 @@ test.describe('Editor tests', () => {
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)`)
|
|> extrude(5, %)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(
|
||||||
|
`Can use the import stdlib function on a local OBJ file`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
const bracketDir = join(dir, 'cube')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cube.obj'),
|
||||||
|
join(bracketDir, 'cube.obj')
|
||||||
|
)
|
||||||
|
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
|
await page.setViewportSize(viewportSize)
|
||||||
|
|
||||||
|
// Locators and constants
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const projectLink = page.getByRole('link', { name: 'cube' })
|
||||||
|
const gizmo = page.locator('[aria-label*=gizmo]')
|
||||||
|
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
|
||||||
|
const locationToHavColor = async (
|
||||||
|
position: { x: number; y: number },
|
||||||
|
color: [number, number, number]
|
||||||
|
) => {
|
||||||
|
return u.getGreatestPixDiff(position, color)
|
||||||
|
}
|
||||||
|
const notTheOrigin = {
|
||||||
|
x: viewportSize.width * 0.55,
|
||||||
|
y: viewportSize.height * 0.3,
|
||||||
|
}
|
||||||
|
const origin = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||||
|
const errorIndicators = page.locator('.cm-lint-marker-error')
|
||||||
|
|
||||||
|
await test.step(`Open the empty file, see the default planes`, async () => {
|
||||||
|
await projectLink.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => locationToHavColor(notTheOrigin, darkModePlaneColorXZ),
|
||||||
|
{
|
||||||
|
timeout: 5000,
|
||||||
|
message: 'XZ plane color is visible',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toBeLessThan(15)
|
||||||
|
})
|
||||||
|
await test.step(`Write the import function line`, async () => {
|
||||||
|
await u.codeLocator.fill(`import('cube.obj')`)
|
||||||
|
await page.waitForTimeout(800)
|
||||||
|
})
|
||||||
|
await test.step(`Reset the camera before checking`, async () => {
|
||||||
|
await u.doAndWaitForCmd(async () => {
|
||||||
|
await gizmo.click({ button: 'right' })
|
||||||
|
await resetCameraButton.click()
|
||||||
|
}, 'zoom_to_fit')
|
||||||
|
})
|
||||||
|
await test.step(`Verify that we see the imported geometry and no errors`, async () => {
|
||||||
|
await expect(errorIndicators).toHaveCount(0)
|
||||||
|
await expect
|
||||||
|
.poll(async () => locationToHavColor(origin, darkModePlaneColorXZ), {
|
||||||
|
timeout: 3000,
|
||||||
|
message: 'Plane color should not be visible',
|
||||||
|
})
|
||||||
|
.toBeGreaterThan(15)
|
||||||
|
await expect
|
||||||
|
.poll(async () => locationToHavColor(origin, darkModeBgColor), {
|
||||||
|
timeout: 3000,
|
||||||
|
message: 'Background color should not be visible',
|
||||||
|
})
|
||||||
|
.toBeGreaterThan(15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -960,4 +960,171 @@ _test.describe('Deleting items from the file pane', () => {
|
|||||||
'TODO - delete folder we are in, with no main.kcl',
|
'TODO - delete folder we are in, with no main.kcl',
|
||||||
async () => {}
|
async () => {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Copied from tests above.
|
||||||
|
_test(
|
||||||
|
`external deletion of project navigates back home`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const TEST_PROJECT_NAME = 'Test Project'
|
||||||
|
const {
|
||||||
|
electronApp,
|
||||||
|
page,
|
||||||
|
dir: projectsDirName,
|
||||||
|
} = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
await fsp.mkdir(join(dir, TEST_PROJECT_NAME), { recursive: true })
|
||||||
|
await fsp.mkdir(join(dir, TEST_PROJECT_NAME, 'folderToDelete'), {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
|
join(dir, TEST_PROJECT_NAME, 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
join(dir, TEST_PROJECT_NAME, 'folderToDelete', 'someFileWithin.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const projectCard = page.getByText(TEST_PROJECT_NAME)
|
||||||
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
|
const folderToDelete = page.getByRole('button', {
|
||||||
|
name: 'folderToDelete',
|
||||||
|
})
|
||||||
|
const fileWithinFolder = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||||
|
})
|
||||||
|
|
||||||
|
await _test.step(
|
||||||
|
'Open project and navigate into folderToDelete',
|
||||||
|
async () => {
|
||||||
|
await projectCard.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
await u.closeKclCodePanel()
|
||||||
|
await u.openFilePanel()
|
||||||
|
|
||||||
|
await folderToDelete.click()
|
||||||
|
await _expect(fileWithinFolder).toBeVisible()
|
||||||
|
await fileWithinFolder.click()
|
||||||
|
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Point of divergence. Delete the project folder and see if it goes back
|
||||||
|
// to the home view.
|
||||||
|
await _test.step(
|
||||||
|
'Delete projectsDirName/<project-name> externally',
|
||||||
|
async () => {
|
||||||
|
await fsp.rm(join(projectsDirName, TEST_PROJECT_NAME), {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await _test.step('Check the app is back on the home view', async () => {
|
||||||
|
const projectsDirLink = page.getByText('Loaded from')
|
||||||
|
await _expect(projectsDirLink).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Similar to the above
|
||||||
|
_test(
|
||||||
|
`external deletion of file in sub-directory updates the file tree and recreates it on code editor typing`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const TEST_PROJECT_NAME = 'Test Project'
|
||||||
|
const {
|
||||||
|
electronApp,
|
||||||
|
page,
|
||||||
|
dir: projectsDirName,
|
||||||
|
} = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
await fsp.mkdir(join(dir, TEST_PROJECT_NAME), { recursive: true })
|
||||||
|
await fsp.mkdir(join(dir, TEST_PROJECT_NAME, 'folderToDelete'), {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
|
join(dir, TEST_PROJECT_NAME, 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
join(dir, TEST_PROJECT_NAME, 'folderToDelete', 'someFileWithin.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const projectCard = page.getByText(TEST_PROJECT_NAME)
|
||||||
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
|
const folderToDelete = page.getByRole('button', {
|
||||||
|
name: 'folderToDelete',
|
||||||
|
})
|
||||||
|
const fileWithinFolder = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||||
|
})
|
||||||
|
|
||||||
|
await _test.step(
|
||||||
|
'Open project and navigate into folderToDelete',
|
||||||
|
async () => {
|
||||||
|
await projectCard.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
|
||||||
|
await folderToDelete.click()
|
||||||
|
await _expect(fileWithinFolder).toBeVisible()
|
||||||
|
await fileWithinFolder.click()
|
||||||
|
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await _test.step(
|
||||||
|
'Delete projectsDirName/<project-name> externally',
|
||||||
|
async () => {
|
||||||
|
await fsp.rm(
|
||||||
|
join(
|
||||||
|
projectsDirName,
|
||||||
|
TEST_PROJECT_NAME,
|
||||||
|
'folderToDelete',
|
||||||
|
'someFileWithin.kcl'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await _test.step('Check the file is gone in the file tree', async () => {
|
||||||
|
await _expect(
|
||||||
|
page.getByTestId('file-pane-scroll-container')
|
||||||
|
).not.toContainText('someFileWithin.kcl')
|
||||||
|
})
|
||||||
|
|
||||||
|
await _test.step(
|
||||||
|
'Check the file is back in the file tree after typing in code editor',
|
||||||
|
async () => {
|
||||||
|
await u.pasteCodeInEditor('hello = 1')
|
||||||
|
await _expect(
|
||||||
|
page.getByTestId('file-pane-scroll-container')
|
||||||
|
).toContainText('someFileWithin.kcl')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -255,7 +255,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
},
|
},
|
||||||
{ timeout: 15_000 }
|
{ timeout: 15_000 }
|
||||||
)
|
)
|
||||||
.toBe(431341)
|
.toBeGreaterThan(300_000)
|
||||||
|
|
||||||
// clean up output.gltf
|
// clean up output.gltf
|
||||||
await fsp.rm('output.gltf')
|
await fsp.rm('output.gltf')
|
||||||
@ -851,7 +851,7 @@ test(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test.fixme(
|
||||||
'When the project folder is empty, user can create new project and open it.',
|
'When the project folder is empty, user can create new project and open it.',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
@ -861,6 +861,12 @@ test(
|
|||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
// Locators and constants
|
||||||
|
const gizmo = page.locator('[aria-label*=gizmo]')
|
||||||
|
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
|
||||||
|
const pointOnModel = { x: 660, y: 250 }
|
||||||
|
const expectedStartCamZPosition = 15633.47
|
||||||
|
|
||||||
// expect to see text "No Projects found"
|
// expect to see text "No Projects found"
|
||||||
await expect(page.getByText('No Projects found')).toBeVisible()
|
await expect(page.getByText('No Projects found')).toBeVisible()
|
||||||
|
|
||||||
@ -873,16 +879,7 @@ test(
|
|||||||
|
|
||||||
await page.getByText('project-000').click()
|
await page.getByText('project-000').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await u.waitForPageLoad()
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).toBeEnabled({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.locator('.cm-content').fill(`sketch001 = startSketchOn('XZ')
|
await page.locator('.cm-content').fill(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-87.4, 282.92], %)
|
|> startProfileAt([-87.4, 282.92], %)
|
||||||
@ -892,8 +889,28 @@ test(
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(200, sketch001)`)
|
extrude001 = extrude(200, sketch001)`)
|
||||||
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
const pointOnModel = { x: 660, y: 250 }
|
async function getCameraZValue() {
|
||||||
|
return page
|
||||||
|
.getByTestId('cam-z-position')
|
||||||
|
.inputValue()
|
||||||
|
.then((value) => parseFloat(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step(`Reset camera`, async () => {
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.doAndWaitForCmd(async () => {
|
||||||
|
await gizmo.click({ button: 'right' })
|
||||||
|
await resetCameraButton.click()
|
||||||
|
}, 'zoom_to_fit')
|
||||||
|
await expect
|
||||||
|
.poll(getCameraZValue, {
|
||||||
|
message: 'Camera Z should be at expected position after reset',
|
||||||
|
})
|
||||||
|
.toEqual(expectedStartCamZPosition)
|
||||||
|
})
|
||||||
|
|
||||||
// gray at this pixel means the stream has loaded in the most
|
// gray at this pixel means the stream has loaded in the most
|
||||||
// user way we can verify it (pixel color)
|
// user way we can verify it (pixel color)
|
||||||
@ -901,7 +918,7 @@ extrude001 = extrude(200, sketch001)`)
|
|||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [143, 143, 143]), {
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [143, 143, 143]), {
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
.toBeLessThan(15)
|
.toBeLessThan(30)
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await page.mouse.move(0, 0, { steps: 5 })
|
await page.mouse.move(0, 0, { steps: 5 })
|
||||||
|
@ -471,7 +471,7 @@ test(
|
|||||||
|
|
||||||
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
||||||
|
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
@ -528,6 +528,7 @@ test(
|
|||||||
// Draw the rectangle
|
// Draw the rectangle
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 30)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 30)
|
||||||
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 })
|
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 })
|
||||||
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
// Ensure the draft rectangle looks the same as it usually does
|
// Ensure the draft rectangle looks the same as it usually does
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
@ -669,6 +670,7 @@ test.describe(
|
|||||||
// screen shot should show the sketch
|
// screen shot should show the sketch
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
|
|
||||||
// exit sketch
|
// exit sketch
|
||||||
@ -686,6 +688,7 @@ test.describe(
|
|||||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -893,7 +896,7 @@ test(
|
|||||||
// Wait for the second extrusion to appear
|
// Wait for the second extrusion to appear
|
||||||
// TODO: Find a way to truly know that the objects have finished
|
// TODO: Find a way to truly know that the objects have finished
|
||||||
// rendering, because an execution-done message is not sufficient.
|
// rendering, because an execution-done message is not sufficient.
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(2000)
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
@ -937,7 +940,7 @@ test(
|
|||||||
// Wait for the second extrusion to appear
|
// Wait for the second extrusion to appear
|
||||||
// TODO: Find a way to truly know that the objects have finished
|
// TODO: Find a way to truly know that the objects have finished
|
||||||
// rendering, because an execution-done message is not sufficient.
|
// rendering, because an execution-done message is not sufficient.
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(2000)
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
@ -47,6 +47,14 @@ export const commonPoints = {
|
|||||||
num2: 14.44,
|
num2: 14.44,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A semi-reliable color to check the default XZ plane on
|
||||||
|
* in dark mode in the default camera position
|
||||||
|
*/
|
||||||
|
export const darkModePlaneColorXZ: [number, number, number] = [50, 50, 99]
|
||||||
|
|
||||||
|
/** A semi-reliable color to check the default dark mode bg color against */
|
||||||
|
export const darkModeBgColor: [number, number, number] = [27, 27, 27]
|
||||||
|
|
||||||
export const editorSelector = '[role="textbox"][data-language="kcl"]'
|
export const editorSelector = '[role="textbox"][data-language="kcl"]'
|
||||||
type PaneId = 'variables' | 'code' | 'files' | 'logs'
|
type PaneId = 'variables' | 'code' | 'files' | 'logs'
|
||||||
|
|
||||||
@ -463,6 +471,9 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
return test_?.step(
|
return test_?.step(
|
||||||
`Create and select project with text "${hasText}"`,
|
`Create and select project with text "${hasText}"`,
|
||||||
async () => {
|
async () => {
|
||||||
|
// Without this, we get unreliable project creation. It's probably
|
||||||
|
// due to a race between the FS being read and clicking doing something.
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.getByTestId('home-new-file').click()
|
await page.getByTestId('home-new-file').click()
|
||||||
const projectLinksPost = page.getByTestId('project-link')
|
const projectLinksPost = page.getByTestId('project-link')
|
||||||
await projectLinksPost.filter({ hasText }).click()
|
await projectLinksPost.filter({ hasText }).click()
|
||||||
@ -492,6 +503,11 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
|
|
||||||
createNewFile: async (name: string) => {
|
createNewFile: async (name: string) => {
|
||||||
return test?.step(`Create a file named ${name}`, async () => {
|
return test?.step(`Create a file named ${name}`, async () => {
|
||||||
|
// If the application is in the middle of connecting a stream
|
||||||
|
// then creating a new file won't work in the end.
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
await page.getByTestId('create-file-button').click()
|
await page.getByTestId('create-file-button').click()
|
||||||
await page.getByTestId('file-rename-field').fill(name)
|
await page.getByTestId('file-rename-field').fill(name)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
@ -874,8 +890,8 @@ export async function setupElectron({
|
|||||||
appSettings
|
appSettings
|
||||||
? { settings: appSettings }
|
? { settings: appSettings }
|
||||||
: {
|
: {
|
||||||
...TEST_SETTINGS,
|
|
||||||
settings: {
|
settings: {
|
||||||
|
...TEST_SETTINGS,
|
||||||
app: {
|
app: {
|
||||||
...TEST_SETTINGS.app,
|
...TEST_SETTINGS.app,
|
||||||
projectDirectory: projectDirName,
|
projectDirectory: projectDirName,
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
executorInputPath,
|
executorInputPath,
|
||||||
} from './test-utils'
|
} from './test-utils'
|
||||||
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
|
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
|
||||||
import { SETTINGS_FILE_NAME } from 'lib/constants'
|
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
|
||||||
import {
|
import {
|
||||||
TEST_SETTINGS_KEY,
|
TEST_SETTINGS_KEY,
|
||||||
TEST_SETTINGS_CORRUPTED,
|
TEST_SETTINGS_CORRUPTED,
|
||||||
@ -445,6 +445,58 @@ test.describe('Testing settings', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'project settings reload on external change',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const {
|
||||||
|
electronApp,
|
||||||
|
page,
|
||||||
|
dir: projectDirName,
|
||||||
|
} = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
const logoLink = page.getByTestId('app-logo')
|
||||||
|
const projectDirLink = page.getByText('Loaded from')
|
||||||
|
|
||||||
|
await test.step('Wait for project view', async () => {
|
||||||
|
await expect(projectDirLink).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
const projectLinks = page.getByTestId('project-link')
|
||||||
|
const oldCount = await projectLinks.count()
|
||||||
|
await page.getByRole('button', { name: 'New project' }).click()
|
||||||
|
await expect(projectLinks).toHaveCount(oldCount + 1)
|
||||||
|
await projectLinks.filter({ hasText: 'project-000' }).first().click()
|
||||||
|
|
||||||
|
const changeColorFs = async (color: string) => {
|
||||||
|
const tempSettingsFilePath = join(
|
||||||
|
projectDirName,
|
||||||
|
'project-000',
|
||||||
|
PROJECT_SETTINGS_FILE_NAME
|
||||||
|
)
|
||||||
|
await fsp.writeFile(
|
||||||
|
tempSettingsFilePath,
|
||||||
|
`[settings.app]\nthemeColor = "${color}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step('Check the color is first starting as we expect', async () => {
|
||||||
|
await expect(logoLink).toHaveCSS('--primary-hue', '264.5')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Check color of logo changed', async () => {
|
||||||
|
await changeColorFs('99')
|
||||||
|
await expect(logoLink).toHaveCSS('--primary-hue', '99')
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
`Closing settings modal should go back to the original file being viewed`,
|
`Closing settings modal should go back to the original file being viewed`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
|
9
interface.d.ts
vendored
@ -20,10 +20,11 @@ export interface IElectronAPI {
|
|||||||
version: typeof process.env.version
|
version: typeof process.env.version
|
||||||
watchFileOn: (
|
watchFileOn: (
|
||||||
path: string,
|
path: string,
|
||||||
|
key: string,
|
||||||
callback: (eventType: string, path: string) => void
|
callback: (eventType: string, path: string) => void
|
||||||
) => void
|
) => void
|
||||||
watchFileOff: (path: string) => void
|
readFile: typeof fs.readFile
|
||||||
readFile: (path: string) => ReturnType<fs.readFile>
|
watchFileOff: (path: string, key: string) => void
|
||||||
writeFile: (
|
writeFile: (
|
||||||
path: string,
|
path: string,
|
||||||
data: string | Uint8Array
|
data: string | Uint8Array
|
||||||
@ -67,13 +68,13 @@ export interface IElectronAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
kittycad: (access: string, args: any) => any
|
kittycad: (access: string, args: any) => any
|
||||||
listMachines: () => Promise<MachinesListing>
|
listMachines: (machineApiIp: string) => Promise<MachinesListing>
|
||||||
getMachineApiIp: () => Promise<string | null>
|
getMachineApiIp: () => Promise<string | null>
|
||||||
onUpdateDownloadStart: (
|
onUpdateDownloadStart: (
|
||||||
callback: (value: { version: string }) => void
|
callback: (value: { version: string }) => void
|
||||||
) => Electron.IpcRenderer
|
) => Electron.IpcRenderer
|
||||||
onUpdateDownloaded: (
|
onUpdateDownloaded: (
|
||||||
callback: (value: string) => void
|
callback: (value: { version: string; releaseNotes: string }) => void
|
||||||
) => Electron.IpcRenderer
|
) => Electron.IpcRenderer
|
||||||
onUpdateError: (callback: (value: { error: Error }) => void) => Electron
|
onUpdateError: (callback: (value: { error: Error }) => void) => Electron
|
||||||
appRestart: () => void
|
appRestart: () => void
|
||||||
|
@ -36,38 +36,297 @@
|
|||||||
"description": "Extra machine-specific information regarding a connected machine.",
|
"description": "Extra machine-specific information regarding a connected machine.",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"additionalProperties": false,
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"Moonraker": {
|
"type": {
|
||||||
"type": "object"
|
"enum": [
|
||||||
|
"moonraker"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"Moonraker"
|
"type"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"additionalProperties": false,
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"Usb": {
|
"type": {
|
||||||
"type": "object"
|
"enum": [
|
||||||
|
"usb"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"Usb"
|
"type"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"additionalProperties": false,
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"Bambu": {
|
"current_stage": {
|
||||||
"type": "object"
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Stage"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The current stage of the machine as defined by Bambu which can include errors, etc.",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"nozzle_diameter": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/NozzleDiameter"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The nozzle diameter of the machine."
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"bambu"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"Bambu"
|
"nozzle_diameter",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"FdmHardwareConfiguration": {
|
||||||
|
"description": "Configuration for a FDM-based printer.",
|
||||||
|
"properties": {
|
||||||
|
"filaments": {
|
||||||
|
"description": "The filaments the printer has access to.",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Filament"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"nozzle_diameter": {
|
||||||
|
"description": "Diameter of the extrusion nozzle, in mm.",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"filaments",
|
||||||
|
"nozzle_diameter"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Filament": {
|
||||||
|
"description": "Information about the filament being used in a FDM printer.",
|
||||||
|
"properties": {
|
||||||
|
"color": {
|
||||||
|
"description": "The color (as hex without the `#`) of the filament, this is likely specific to the manufacturer.",
|
||||||
|
"maxLength": 6,
|
||||||
|
"minLength": 6,
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"material": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/FilamentMaterial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The material that the filament is made of."
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "The name of the filament, this is likely specfic to the manufacturer.",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"material"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"FilamentMaterial": {
|
||||||
|
"description": "The material that the filament is made of.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "Polylactic acid based plastics",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"pla"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Pla support",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"pla_support"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "acrylonitrile butadiene styrene based plastics",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"abs"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "polyethylene terephthalate glycol based plastics",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"petg"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "unsuprisingly, nylon based",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"nylon"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "thermoplastic polyurethane based urethane material",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"tpu"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "polyvinyl alcohol based material",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"pva"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "high impact polystyrene based material",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"hips"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "composite material with stuff in other stuff, something like PLA mixed with carbon fiber, kevlar, or fiberglass",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"composite"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"HardwareConfiguration": {
|
||||||
|
"description": "The hardware configuration of a machine.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "No configuration is possible. This isn't the same conceptually as an `Option<HardwareConfiguration>`, because this indicates we positively know there is no possible configuration changes that are possible with this method of manufcture.",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Hardware configuration specific to FDM based printers",
|
||||||
|
"properties": {
|
||||||
|
"config": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/FdmHardwareConfiguration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The configuration for the FDM printer."
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"fdm"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"config",
|
||||||
|
"type"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
}
|
}
|
||||||
@ -85,6 +344,14 @@
|
|||||||
"description": "Additional, per-machine information which is specific to the underlying machine type.",
|
"description": "Additional, per-machine information which is specific to the underlying machine type.",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
|
"hardware_configuration": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/HardwareConfiguration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Information about how the Machine is currently configured."
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"description": "Machine Identifier (ID) for the specific Machine.",
|
"description": "Machine Identifier (ID) for the specific Machine.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -114,6 +381,12 @@
|
|||||||
"description": "Maximum part size that can be manufactured by this device. This may be some sort of theoretical upper bound, getting close to this limit seems like maybe a bad idea.\n\nThis may be `None` if the maximum size is not knowable by the Machine API.\n\nWhat \"close\" means is up to you!",
|
"description": "Maximum part size that can be manufactured by this device. This may be some sort of theoretical upper bound, getting close to this limit seems like maybe a bad idea.\n\nThis may be `None` if the maximum size is not knowable by the Machine API.\n\nWhat \"close\" means is up to you!",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
|
"progress": {
|
||||||
|
"description": "Progress of the current print, if printing.",
|
||||||
|
"format": "double",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
@ -124,6 +397,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
"hardware_configuration",
|
||||||
"id",
|
"id",
|
||||||
"machine_type",
|
"machine_type",
|
||||||
"make_model",
|
"make_model",
|
||||||
@ -157,57 +431,111 @@
|
|||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"description": "If a print state can not be resolved at this time, an Unknown may be returned.",
|
"description": "If a print state can not be resolved at this time, an Unknown may be returned.",
|
||||||
"enum": [
|
|
||||||
"Unknown"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Idle, and ready for another job.",
|
|
||||||
"enum": [
|
|
||||||
"Idle"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Running a job -- 3D printing or CNC-ing a part.",
|
|
||||||
"enum": [
|
|
||||||
"Running"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Machine is currently offline or unreachable.",
|
|
||||||
"enum": [
|
|
||||||
"Offline"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Job is underway but halted, waiting for some action to take place.",
|
|
||||||
"enum": [
|
|
||||||
"Paused"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Job is finished, but waiting manual action to move back to Idle.",
|
|
||||||
"enum": [
|
|
||||||
"Complete"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"additionalProperties": false,
|
|
||||||
"description": "The printer has failed and is in an unknown state that may require manual attention to resolve. The inner value is a human readable description of what specifically has failed.",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"Failed": {
|
"state": {
|
||||||
"nullable": true,
|
"enum": [
|
||||||
|
"unknown"
|
||||||
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"Failed"
|
"state"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Idle, and ready for another job.",
|
||||||
|
"properties": {
|
||||||
|
"state": {
|
||||||
|
"enum": [
|
||||||
|
"idle"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Running a job -- 3D printing or CNC-ing a part.",
|
||||||
|
"properties": {
|
||||||
|
"state": {
|
||||||
|
"enum": [
|
||||||
|
"running"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Machine is currently offline or unreachable.",
|
||||||
|
"properties": {
|
||||||
|
"state": {
|
||||||
|
"enum": [
|
||||||
|
"offline"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Job is underway but halted, waiting for some action to take place.",
|
||||||
|
"properties": {
|
||||||
|
"state": {
|
||||||
|
"enum": [
|
||||||
|
"paused"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Job is finished, but waiting manual action to move back to Idle.",
|
||||||
|
"properties": {
|
||||||
|
"state": {
|
||||||
|
"enum": [
|
||||||
|
"complete"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"state"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The printer has failed and is in an unknown state that may require manual attention to resolve. The inner value is a human readable description of what specifically has failed.",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"description": "A human-readable message describing the failure.",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"enum": [
|
||||||
|
"failed"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"state"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
}
|
}
|
||||||
@ -219,21 +547,54 @@
|
|||||||
{
|
{
|
||||||
"description": "Use light to cure a resin to build up layers.",
|
"description": "Use light to cure a resin to build up layers.",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Stereolithography"
|
"stereolithography"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Fused Deposition Modeling, layers of melted plastic.",
|
"description": "Fused Deposition Modeling, layers of melted plastic.",
|
||||||
"enum": [
|
"enum": [
|
||||||
"FusedDeposition"
|
"fused_deposition"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "\"Computer numerical control\" - machine that grinds away material from a hunk of material to construct a part.",
|
"description": "\"Computer numerical control\" - machine that grinds away material from a hunk of material to construct a part.",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Cnc"
|
"cnc"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NozzleDiameter": {
|
||||||
|
"description": "A nozzle diameter.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "0.2mm.",
|
||||||
|
"enum": [
|
||||||
|
"0.2"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "0.4mm.",
|
||||||
|
"enum": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "0.6mm.",
|
||||||
|
"enum": [
|
||||||
|
"0.6"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "0.8mm.",
|
||||||
|
"enum": [
|
||||||
|
"0.8"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -284,6 +645,15 @@
|
|||||||
"machine_id": {
|
"machine_id": {
|
||||||
"description": "The machine id to print to.",
|
"description": "The machine id to print to.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"slicer_configuration": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/SlicerConfiguration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Requested design-specific slicer configurations.",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@ -292,6 +662,283 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SlicerConfiguration": {
|
||||||
|
"description": "The slicer configuration is a set of parameters that are passed to the slicer to control how the gcode is generated.",
|
||||||
|
"properties": {
|
||||||
|
"filament_idx": {
|
||||||
|
"description": "The filament to use for the print.",
|
||||||
|
"format": "uint",
|
||||||
|
"minimum": 0,
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Stage": {
|
||||||
|
"description": "The print stage. These come from: https://github.com/SoftFever/OrcaSlicer/blob/431978baf17961df90f0d01871b0ad1d839d7f5d/src/slic3r/GUI/DeviceManager.cpp#L78",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "Nothing.",
|
||||||
|
"enum": [
|
||||||
|
"nothing"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Empty.",
|
||||||
|
"enum": [
|
||||||
|
"empty"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Auto bed leveling.",
|
||||||
|
"enum": [
|
||||||
|
"auto_bed_leveling"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Heatbed preheating.",
|
||||||
|
"enum": [
|
||||||
|
"heatbed_preheating"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Sweeping XY mech mode.",
|
||||||
|
"enum": [
|
||||||
|
"sweeping_xy_mech_mode"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Changing filament.",
|
||||||
|
"enum": [
|
||||||
|
"changing_filament"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "M400 pause.",
|
||||||
|
"enum": [
|
||||||
|
"m400_pause"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Paused due to filament runout.",
|
||||||
|
"enum": [
|
||||||
|
"paused_due_to_filament_runout"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Heating hotend.",
|
||||||
|
"enum": [
|
||||||
|
"heating_hotend"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Calibrating extrusion.",
|
||||||
|
"enum": [
|
||||||
|
"calibrating_extrusion"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Scanning bed surface.",
|
||||||
|
"enum": [
|
||||||
|
"scanning_bed_surface"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Inspecting first layer.",
|
||||||
|
"enum": [
|
||||||
|
"inspecting_first_layer"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Identifying build plate type.",
|
||||||
|
"enum": [
|
||||||
|
"identifying_build_plate_type"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Calibrating micro lidar.",
|
||||||
|
"enum": [
|
||||||
|
"calibrating_micro_lidar"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Homing toolhead.",
|
||||||
|
"enum": [
|
||||||
|
"homing_toolhead"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Cleaning nozzle tip.",
|
||||||
|
"enum": [
|
||||||
|
"cleaning_nozzle_tip"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Checking extruder temperature.",
|
||||||
|
"enum": [
|
||||||
|
"checking_extruder_temperature"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Printing was paused by the user.",
|
||||||
|
"enum": [
|
||||||
|
"printing_was_paused_by_the_user"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Pause of front cover falling.",
|
||||||
|
"enum": [
|
||||||
|
"pause_of_front_cover_falling"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Calibrating micro lidar.",
|
||||||
|
"enum": [
|
||||||
|
"calibrating_micro_lidar2"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Calibrating extrusion flow.",
|
||||||
|
"enum": [
|
||||||
|
"calibrating_extrusion_flow"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Paused due to nozzle temperature malfunction.",
|
||||||
|
"enum": [
|
||||||
|
"paused_due_to_nozzle_temperature_malfunction"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Paused due to heat bed temperature malfunction.",
|
||||||
|
"enum": [
|
||||||
|
"paused_due_to_heat_bed_temperature_malfunction"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Filament unloading.",
|
||||||
|
"enum": [
|
||||||
|
"filament_unloading"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Skip step pause.",
|
||||||
|
"enum": [
|
||||||
|
"skip_step_pause"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Filament loading.",
|
||||||
|
"enum": [
|
||||||
|
"filament_loading"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Motor noise calibration.",
|
||||||
|
"enum": [
|
||||||
|
"motor_noise_calibration"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Paused due to AMS lost.",
|
||||||
|
"enum": [
|
||||||
|
"paused_due_to_ams_lost"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Paused due to low speed of the heat break fan.",
|
||||||
|
"enum": [
|
||||||
|
"paused_due_to_low_speed_of_the_heat_break_fan"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Paused due to chamber temperature control error.",
|
||||||
|
"enum": [
|
||||||
|
"paused_due_to_chamber_temperature_control_error"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Cooling chamber.",
|
||||||
|
"enum": [
|
||||||
|
"cooling_chamber"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Paused by the Gcode inserted by the user.",
|
||||||
|
"enum": [
|
||||||
|
"paused_by_the_gcode_inserted_by_the_user"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Motor noise showoff.",
|
||||||
|
"enum": [
|
||||||
|
"motor_noise_showoff"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Nozzle filament covered detected pause.",
|
||||||
|
"enum": [
|
||||||
|
"nozzle_filament_covered_detected_pause"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Cutter error pause.",
|
||||||
|
"enum": [
|
||||||
|
"cutter_error_pause"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "First layer error pause.",
|
||||||
|
"enum": [
|
||||||
|
"first_layer_error_pause"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Nozzle clog pause.",
|
||||||
|
"enum": [
|
||||||
|
"nozzle_clog_pause"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"Volume": {
|
"Volume": {
|
||||||
"description": "Set of three values to represent the extent of a 3-D Volume. This contains the width, depth, and height values, generally used to represent some maximum or minimum.\n\nAll measurements are in millimeters.",
|
"description": "Set of three values to represent the extent of a 3-D Volume. This contains the width, depth, and height values, generally used to represent some maximum or minimum.\n\nAll measurements are in millimeters.",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -425,6 +1072,34 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/metrics": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "get_metrics",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"title": "String",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "successful operation"
|
||||||
|
},
|
||||||
|
"4XX": {
|
||||||
|
"$ref": "#/components/responses/Error"
|
||||||
|
},
|
||||||
|
"5XX": {
|
||||||
|
"$ref": "#/components/responses/Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": "List available machines and their statuses",
|
||||||
|
"tags": [
|
||||||
|
"hidden"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/ping": {
|
"/ping": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "ping",
|
"operationId": "ping",
|
||||||
@ -492,6 +1167,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
|
{
|
||||||
|
"description": "Hidden API endpoints that should not show up in the docs.",
|
||||||
|
"externalDocs": {
|
||||||
|
"url": "https://docs.zoo.dev/api/machines"
|
||||||
|
},
|
||||||
|
"name": "hidden"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Utilities for making parts and discovering machines.",
|
"description": "Utilities for making parts and discovering machines.",
|
||||||
"externalDocs": {
|
"externalDocs": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zoo-modeling-app",
|
"name": "zoo-modeling-app",
|
||||||
"version": "0.25.6",
|
"version": "0.26.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"author": {
|
"author": {
|
||||||
|
@ -135,7 +135,9 @@ function CommandArgOptionInput({
|
|||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
id="option-input"
|
id="option-input"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
onChange={(event) => setQuery(event.target.value)}
|
onChange={(event) =>
|
||||||
|
!event.target.disabled && setQuery(event.target.value)
|
||||||
|
}
|
||||||
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
|
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.metaKey && event.key === 'k')
|
if (event.metaKey && event.key === 'k')
|
||||||
@ -175,9 +177,18 @@ function CommandArgOptionInput({
|
|||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
key={option.name}
|
key={option.name}
|
||||||
value={option}
|
value={option}
|
||||||
|
disabled={option.disabled}
|
||||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||||
>
|
>
|
||||||
<p className="flex-grow">{option.name} </p>
|
<p
|
||||||
|
className={`flex-grow ${
|
||||||
|
(option.disabled &&
|
||||||
|
'text-chalkboard-70 dark:text-chalkboard-50 cursor-not-allowed') ||
|
||||||
|
''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{option.name}
|
||||||
|
</p>
|
||||||
{option.value === currentOption?.value && (
|
{option.value === currentOption?.value && (
|
||||||
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
||||||
current
|
current
|
||||||
|
@ -2,7 +2,7 @@ import type { IndexLoaderData } from 'lib/types'
|
|||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
import { Dispatch, useCallback, useEffect, useRef, useState } from 'react'
|
import { Dispatch, useCallback, useRef, useState } from 'react'
|
||||||
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||||
import { Disclosure } from '@headlessui/react'
|
import { Disclosure } from '@headlessui/react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
@ -13,7 +13,6 @@ import { sortProject } from 'lib/desktopFS'
|
|||||||
import { FILE_EXT } from 'lib/constants'
|
import { FILE_EXT } from 'lib/constants'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { codeManager, kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
|
|
||||||
import { useLspContext } from './LspProvider'
|
import { useLspContext } from './LspProvider'
|
||||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
@ -21,6 +20,8 @@ import { DeleteConfirmationDialog } from './ProjectCard/DeleteProjectDialog'
|
|||||||
import { ContextMenu, ContextMenuItem } from './ContextMenu'
|
import { ContextMenu, ContextMenuItem } from './ContextMenu'
|
||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
import { FileEntry } from 'lib/project'
|
import { FileEntry } from 'lib/project'
|
||||||
|
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||||
|
import { normalizeLineEndings } from 'lib/codeEditor'
|
||||||
|
|
||||||
function getIndentationCSS(level: number) {
|
function getIndentationCSS(level: number) {
|
||||||
return `calc(1rem * ${level + 1})`
|
return `calc(1rem * ${level + 1})`
|
||||||
@ -131,6 +132,23 @@ const FileTreeItem = ({
|
|||||||
const isCurrentFile = fileOrDir.path === currentFile?.path
|
const isCurrentFile = fileOrDir.path === currentFile?.path
|
||||||
const itemRef = useRef(null)
|
const itemRef = useRef(null)
|
||||||
|
|
||||||
|
// Since every file or directory gets its own FileTreeItem, we can do this.
|
||||||
|
// Because subtrees only render when they are opened, that means this
|
||||||
|
// only listens when they open. Because this acts like a useEffect, when
|
||||||
|
// the ReactNodes are destroyed, so is this listener :)
|
||||||
|
useFileSystemWatcher(
|
||||||
|
async (eventType, path) => {
|
||||||
|
// Don't try to read a file that was removed.
|
||||||
|
if (isCurrentFile && eventType !== 'unlink') {
|
||||||
|
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
||||||
|
code = normalizeLineEndings(code)
|
||||||
|
codeManager.updateCodeStateEditor(code)
|
||||||
|
}
|
||||||
|
fileSend({ type: 'Refresh' })
|
||||||
|
},
|
||||||
|
[fileOrDir.path]
|
||||||
|
)
|
||||||
|
|
||||||
const isRenaming = fileContext.itemsBeingRenamed.includes(fileOrDir.path)
|
const isRenaming = fileContext.itemsBeingRenamed.includes(fileOrDir.path)
|
||||||
const removeCurrentItemFromRenaming = useCallback(
|
const removeCurrentItemFromRenaming = useCallback(
|
||||||
() =>
|
() =>
|
||||||
@ -154,6 +172,13 @@ const FileTreeItem = ({
|
|||||||
})
|
})
|
||||||
}, [fileContext.itemsBeingRenamed, fileOrDir.path, fileSend])
|
}, [fileContext.itemsBeingRenamed, fileOrDir.path, fileSend])
|
||||||
|
|
||||||
|
const clickDirectory = () => {
|
||||||
|
fileSend({
|
||||||
|
type: 'Set selected directory',
|
||||||
|
directory: fileOrDir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function handleKeyUp(e: React.KeyboardEvent<HTMLButtonElement>) {
|
function handleKeyUp(e: React.KeyboardEvent<HTMLButtonElement>) {
|
||||||
if (e.metaKey && e.key === 'Backspace') {
|
if (e.metaKey && e.key === 'Backspace') {
|
||||||
// Open confirmation dialog
|
// Open confirmation dialog
|
||||||
@ -242,18 +267,8 @@ const FileTreeItem = ({
|
|||||||
}
|
}
|
||||||
style={{ paddingInlineStart: getIndentationCSS(level) }}
|
style={{ paddingInlineStart: getIndentationCSS(level) }}
|
||||||
onClick={(e) => e.currentTarget.focus()}
|
onClick={(e) => e.currentTarget.focus()}
|
||||||
onClickCapture={(e) =>
|
onClickCapture={clickDirectory}
|
||||||
fileSend({
|
onFocusCapture={clickDirectory}
|
||||||
type: 'Set selected directory',
|
|
||||||
directory: fileOrDir,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onFocusCapture={(e) =>
|
|
||||||
fileSend({
|
|
||||||
type: 'Set selected directory',
|
|
||||||
directory: fileOrDir,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
|
onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
>
|
>
|
||||||
@ -469,27 +484,30 @@ export const FileTreeInner = ({
|
|||||||
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||||
const { send: fileSend, context: fileContext } = useFileContext()
|
const { send: fileSend, context: fileContext } = useFileContext()
|
||||||
const { send: modelingSend } = useModelingContext()
|
const { send: modelingSend } = useModelingContext()
|
||||||
const documentHasFocus = useDocumentHasFocus()
|
|
||||||
|
|
||||||
// Refresh the file tree when the document gets focus
|
// Refresh the file tree when there are changes.
|
||||||
useEffect(() => {
|
useFileSystemWatcher(
|
||||||
fileSend({ type: 'Refresh' })
|
async (eventType, path) => {
|
||||||
}, [documentHasFocus])
|
fileSend({ type: 'Refresh' })
|
||||||
|
},
|
||||||
|
[loaderData?.project?.path, fileContext.selectedDirectory.path].filter(
|
||||||
|
(x: string | undefined) => x !== undefined
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const clickDirectory = () => {
|
||||||
|
fileSend({
|
||||||
|
type: 'Set selected directory',
|
||||||
|
directory: fileContext.project,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="overflow-auto pb-12 absolute inset-0"
|
className="overflow-auto pb-12 absolute inset-0"
|
||||||
data-testid="file-pane-scroll-container"
|
data-testid="file-pane-scroll-container"
|
||||||
>
|
>
|
||||||
<ul
|
<ul className="m-0 p-0 text-sm" onClickCapture={clickDirectory}>
|
||||||
className="m-0 p-0 text-sm"
|
|
||||||
onClickCapture={(e) => {
|
|
||||||
fileSend({
|
|
||||||
type: 'Set selected directory',
|
|
||||||
directory: fileContext.project,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{sortProject(fileContext.project?.children || []).map((fileOrDir) => (
|
{sortProject(fileContext.project?.children || []).map((fileOrDir) => (
|
||||||
<FileTreeItem
|
<FileTreeItem
|
||||||
project={fileContext.project}
|
project={fileContext.project}
|
||||||
|
@ -11,6 +11,7 @@ export const NetworkMachineIndicator = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const machineCount = machineManager.machineCount()
|
const machineCount = machineManager.machineCount()
|
||||||
const reason = machineManager.noMachinesReason()
|
const reason = machineManager.noMachinesReason()
|
||||||
|
const machines = machineManager.machines
|
||||||
|
|
||||||
return isDesktop() ? (
|
return isDesktop() ? (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
@ -46,20 +47,34 @@ export const NetworkMachineIndicator = ({
|
|||||||
</div>
|
</div>
|
||||||
{machineCount > 0 && (
|
{machineCount > 0 && (
|
||||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||||
{Object.entries(machineManager.machines).map(
|
{machines.map((machine) => {
|
||||||
([hostname, machine]) => (
|
return (
|
||||||
<li key={hostname} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
||||||
<p className="">
|
<p className="">{machine.id.toUpperCase()}</p>
|
||||||
{machine.make_model.model ||
|
|
||||||
machine.make_model.manufacturer ||
|
|
||||||
'Unknown Machine'}
|
|
||||||
</p>
|
|
||||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||||
Hostname {hostname}
|
{machine.make_model.model}
|
||||||
|
</p>
|
||||||
|
{machine.extra &&
|
||||||
|
machine.extra.type === 'bambu' &&
|
||||||
|
machine.extra.nozzle_diameter && (
|
||||||
|
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||||
|
Nozzle Diameter: {machine.extra.nozzle_diameter}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||||
|
{`Status: ${machine.state.state
|
||||||
|
.charAt(0)
|
||||||
|
.toUpperCase()}${machine.state.state.slice(1)}`}
|
||||||
|
{machine.state.state === 'failed' && machine.state.message
|
||||||
|
? ` (${machine.state.message})`
|
||||||
|
: ''}
|
||||||
|
{machine.state.state === 'running' && machine.progress
|
||||||
|
? ` (${Math.round(machine.progress)}%)`
|
||||||
|
: ''}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
)}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
|
@ -221,6 +221,19 @@ export const SettingsAuthProviderBase = ({
|
|||||||
|
|
||||||
useFileSystemWatcher(
|
useFileSystemWatcher(
|
||||||
async () => {
|
async () => {
|
||||||
|
// If there is a projectPath but it no longer exists it means
|
||||||
|
// it was exterally removed. If we let the code past this condition
|
||||||
|
// execute it will recreate the directory due to code in
|
||||||
|
// loadAndValidateSettings trying to recreate files. I do not
|
||||||
|
// wish to change the behavior in case anything else uses it.
|
||||||
|
// Go home.
|
||||||
|
if (loadedProject?.project?.path) {
|
||||||
|
if (!window.electron.exists(loadedProject?.project?.path)) {
|
||||||
|
navigate(PATHS.HOME)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const data = await loadAndValidateSettings(loadedProject?.project?.path)
|
const data = await loadAndValidateSettings(loadedProject?.project?.path)
|
||||||
settingsSend({
|
settingsSend({
|
||||||
type: 'Set all settings',
|
type: 'Set all settings',
|
||||||
@ -228,7 +241,9 @@ export const SettingsAuthProviderBase = ({
|
|||||||
doNotPersist: true,
|
doNotPersist: true,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
settingsPath ? [settingsPath] : []
|
[settingsPath, loadedProject?.project?.path].filter(
|
||||||
|
(x: string | undefined) => x !== undefined
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add settings commands to the command bar
|
// Add settings commands to the command bar
|
||||||
|
153
src/components/ToastUpdate.test.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import { vi } from 'vitest'
|
||||||
|
import { ToastUpdate } from './ToastUpdate'
|
||||||
|
|
||||||
|
describe('ToastUpdate tests', () => {
|
||||||
|
const testData = {
|
||||||
|
version: '0.255.255',
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
url: 'Zoo Modeling App-0.255.255-x64-mac.zip',
|
||||||
|
sha512:
|
||||||
|
'VJb0qlrqNr+rVx3QLATz+B28dtHw3osQb5/+UUmQUIMuF9t0i8dTKOVL/2lyJSmLJVw2/SGDB4Ud6VlTPJ6oFw==',
|
||||||
|
size: 141277345,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'Zoo Modeling App-0.255.255-arm64-mac.zip',
|
||||||
|
sha512:
|
||||||
|
'b+ugdg7A4LhYYJaFkPRxh1RvmGGMlPJJj7inkLg9PwRtCnR9ePMlktj2VRciXF1iLh59XW4bLc4dK1dFQHMULA==',
|
||||||
|
size: 135278259,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'Zoo Modeling App-0.255.255-x64-mac.dmg',
|
||||||
|
sha512:
|
||||||
|
'gCUqww05yj8OYwPiTq6bo5GbkpngSbXGtenmDD7+kUm0UyVK8WD3dMAfQJtGNG5HY23aHCHe9myE2W4mbZGmiQ==',
|
||||||
|
size: 146004232,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'Zoo Modeling App-0.255.255-arm64-mac.dmg',
|
||||||
|
sha512:
|
||||||
|
'ND871ayf81F1ZT+iWVLYTc2jdf/Py6KThuxX2QFWz14ebmIbJPL07lNtxQOexOFiuk0MwRhlCy1RzOSG1b9bmw==',
|
||||||
|
size: 140021522,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
path: 'Zoo Modeling App-0.255.255-x64-mac.zip',
|
||||||
|
sha512:
|
||||||
|
'VJb0qlrqNr+rVx3QLATz+B28dtHw3osQb5/+UUmQUIMuF9t0i8dTKOVL/2lyJSmLJVw2/SGDB4Ud6VlTPJ6oFw==',
|
||||||
|
releaseNotes:
|
||||||
|
'## Some markdown release notes\n\n- This is a list item\n- This is another list item\n\n```javascript\nconsole.log("Hello, world!")\n```\n',
|
||||||
|
releaseDate: '2024-10-09T11:57:59.133Z',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
test('Happy path: renders the toast with good data', () => {
|
||||||
|
const onRestart = vi.fn()
|
||||||
|
const onDismiss = vi.fn()
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ToastUpdate
|
||||||
|
onRestart={onRestart}
|
||||||
|
onDismiss={onDismiss}
|
||||||
|
version={testData.version}
|
||||||
|
releaseNotes={testData.releaseNotes}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Locators and other constants
|
||||||
|
const versionText = screen.getByTestId('update-version')
|
||||||
|
const restartButton = screen.getByRole('button', { name: /restart/i })
|
||||||
|
const dismissButton = screen.getByRole('button', { name: /got it/i })
|
||||||
|
const releaseNotes = screen.getByTestId('release-notes')
|
||||||
|
|
||||||
|
expect(versionText).toBeVisible()
|
||||||
|
expect(versionText).toHaveTextContent(testData.version)
|
||||||
|
|
||||||
|
expect(restartButton).toBeEnabled()
|
||||||
|
fireEvent.click(restartButton)
|
||||||
|
expect(onRestart.mock.calls).toHaveLength(1)
|
||||||
|
|
||||||
|
expect(dismissButton).toBeEnabled()
|
||||||
|
fireEvent.click(dismissButton)
|
||||||
|
expect(onDismiss.mock.calls).toHaveLength(1)
|
||||||
|
|
||||||
|
// I cannot for the life of me seem to get @testing-library/react
|
||||||
|
// to properly handle click events or visibility checks on the details element.
|
||||||
|
// So I'm only checking that the content is in the document.
|
||||||
|
expect(releaseNotes).toBeInTheDocument()
|
||||||
|
expect(releaseNotes).toHaveTextContent('Release notes')
|
||||||
|
const releaseNotesListItems = screen.getAllByRole('listitem')
|
||||||
|
expect(releaseNotesListItems.map((el) => el.textContent)).toEqual([
|
||||||
|
'This is a list item',
|
||||||
|
'This is another list item',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Happy path: renders the breaking changes notice', () => {
|
||||||
|
const releaseNotesWithBreakingChanges = `
|
||||||
|
## Some markdown release notes
|
||||||
|
- This is a list item
|
||||||
|
- This is another list item with a breaking change
|
||||||
|
- This is a list item
|
||||||
|
`
|
||||||
|
const onRestart = vi.fn()
|
||||||
|
const onDismiss = vi.fn()
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ToastUpdate
|
||||||
|
onRestart={onRestart}
|
||||||
|
onDismiss={onDismiss}
|
||||||
|
version={testData.version}
|
||||||
|
releaseNotes={releaseNotesWithBreakingChanges}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Locators and other constants
|
||||||
|
const releaseNotes = screen.getByText('Release notes', {
|
||||||
|
selector: 'summary',
|
||||||
|
})
|
||||||
|
const listItemContents = screen
|
||||||
|
.getAllByRole('listitem')
|
||||||
|
.map((el) => el.textContent)
|
||||||
|
|
||||||
|
// I cannot for the life of me seem to get @testing-library/react
|
||||||
|
// to properly handle click events or visibility checks on the details element.
|
||||||
|
// So I'm only checking that the content is in the document.
|
||||||
|
expect(releaseNotes).toBeInTheDocument()
|
||||||
|
expect(listItemContents).toEqual([
|
||||||
|
'This is a list item',
|
||||||
|
'This is another list item with a breaking change',
|
||||||
|
'This is a list item',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Missing release notes: renders the toast without release notes', () => {
|
||||||
|
const onRestart = vi.fn()
|
||||||
|
const onDismiss = vi.fn()
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ToastUpdate
|
||||||
|
onRestart={onRestart}
|
||||||
|
onDismiss={onDismiss}
|
||||||
|
version={testData.version}
|
||||||
|
releaseNotes={''}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Locators and other constants
|
||||||
|
const versionText = screen.getByTestId('update-version')
|
||||||
|
const restartButton = screen.getByRole('button', { name: /restart/i })
|
||||||
|
const dismissButton = screen.getByRole('button', { name: /got it/i })
|
||||||
|
const releaseNotes = screen.queryByText(/release notes/i, {
|
||||||
|
selector: 'details > summary',
|
||||||
|
})
|
||||||
|
const releaseNotesListItem = screen.queryByRole('listitem', {
|
||||||
|
name: /this is a list item/i,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(versionText).toBeVisible()
|
||||||
|
expect(versionText).toHaveTextContent(testData.version)
|
||||||
|
expect(releaseNotes).not.toBeInTheDocument()
|
||||||
|
expect(releaseNotesListItem).not.toBeInTheDocument()
|
||||||
|
expect(restartButton).toBeEnabled()
|
||||||
|
expect(dismissButton).toBeEnabled()
|
||||||
|
})
|
||||||
|
})
|
@ -1,14 +1,23 @@
|
|||||||
import toast from 'react-hot-toast'
|
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'
|
||||||
|
|
||||||
export function ToastUpdate({
|
export function ToastUpdate({
|
||||||
version,
|
version,
|
||||||
|
releaseNotes,
|
||||||
onRestart,
|
onRestart,
|
||||||
|
onDismiss,
|
||||||
}: {
|
}: {
|
||||||
version: string
|
version: string
|
||||||
|
releaseNotes?: string
|
||||||
onRestart: () => void
|
onRestart: () => void
|
||||||
|
onDismiss: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const containsBreakingChanges = releaseNotes
|
||||||
|
?.toLocaleLowerCase()
|
||||||
|
.includes('breaking')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
|
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
|
||||||
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||||
@ -19,7 +28,7 @@ export function ToastUpdate({
|
|||||||
>
|
>
|
||||||
v{version}
|
v{version}
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-4 text-md text-bold">
|
<p className="ml-4 text-md text-bold">
|
||||||
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
|
||||||
@ -32,15 +41,39 @@ export function ToastUpdate({
|
|||||||
>
|
>
|
||||||
here on GitHub.
|
here on GitHub.
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{releaseNotes && (
|
||||||
|
<details
|
||||||
|
className="my-4 border border-chalkboard-30 dark:border-chalkboard-60 rounded"
|
||||||
|
open={containsBreakingChanges}
|
||||||
|
data-testid="release-notes"
|
||||||
|
>
|
||||||
|
<summary className="p-2 select-none cursor-pointer">
|
||||||
|
Release notes
|
||||||
|
{containsBreakingChanges && (
|
||||||
|
<strong className="text-destroy-50"> (Breaking changes)</strong>
|
||||||
|
)}
|
||||||
|
</summary>
|
||||||
|
<div
|
||||||
|
className="parsed-markdown py-2 px-4 mt-2 border-t border-chalkboard-30 dark:border-chalkboard-60 max-h-60 overflow-y-auto"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: Marked.parse(releaseNotes, {
|
||||||
|
gfm: true,
|
||||||
|
breaks: true,
|
||||||
|
sanitize: true,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</details>
|
||||||
|
)}
|
||||||
<div className="flex justify-between gap-8">
|
<div className="flex justify-between gap-8">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
iconStart={{
|
iconStart={{
|
||||||
icon: 'arrowRotateRight',
|
icon: 'arrowRotateRight',
|
||||||
}}
|
}}
|
||||||
name="Restart app now"
|
name="restart"
|
||||||
onClick={onRestart}
|
onClick={onRestart}
|
||||||
>
|
>
|
||||||
Restart app now
|
Restart app now
|
||||||
@ -50,9 +83,10 @@ export function ToastUpdate({
|
|||||||
iconStart={{
|
iconStart={{
|
||||||
icon: 'checkmark',
|
icon: 'checkmark',
|
||||||
}}
|
}}
|
||||||
name="Got it"
|
name="dismiss"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toast.dismiss()
|
toast.dismiss()
|
||||||
|
onDismiss()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Got it
|
Got it
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { styleTags, tags as t } from '@lezer/highlight'
|
import { styleTags, tags as t } from '@lezer/highlight'
|
||||||
|
|
||||||
export const kclHighlight = styleTags({
|
export const kclHighlight = styleTags({
|
||||||
|
'import export': t.moduleKeyword,
|
||||||
|
ImportItemAs: t.definitionKeyword,
|
||||||
|
ImportFrom: t.moduleKeyword,
|
||||||
'fn var let const': t.definitionKeyword,
|
'fn var let const': t.definitionKeyword,
|
||||||
'if else': t.controlKeyword,
|
'if else': t.controlKeyword,
|
||||||
return: t.controlKeyword,
|
return: t.controlKeyword,
|
||||||
@ -8,7 +11,7 @@ export const kclHighlight = styleTags({
|
|||||||
nil: t.null,
|
nil: t.null,
|
||||||
'AddOp MultOp ExpOp': t.arithmeticOperator,
|
'AddOp MultOp ExpOp': t.arithmeticOperator,
|
||||||
BangOp: t.logicOperator,
|
BangOp: t.logicOperator,
|
||||||
CompOp: t.logicOperator,
|
CompOp: t.compareOperator,
|
||||||
'Equals Arrow': t.definitionOperator,
|
'Equals Arrow': t.definitionOperator,
|
||||||
PipeOperator: t.controlOperator,
|
PipeOperator: t.controlOperator,
|
||||||
String: t.string,
|
String: t.string,
|
||||||
|
@ -15,8 +15,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
statement[@isGroup=Statement] {
|
statement[@isGroup=Statement] {
|
||||||
FunctionDeclaration { kw<"fn"> VariableDefinition Equals ParamList Arrow Body } |
|
ImportStatement { kw<"import"> ImportItems ImportFrom String } |
|
||||||
VariableDeclaration { (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals ParamList Arrow Body } |
|
||||||
|
VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
||||||
ReturnStatement { kw<"return"> expression } |
|
ReturnStatement { kw<"return"> expression } |
|
||||||
ExpressionStatement { expression }
|
ExpressionStatement { expression }
|
||||||
}
|
}
|
||||||
@ -25,6 +26,9 @@ ParamList { "(" commaSep<Parameter { VariableDefinition "?"? (":" type)? }> ")"
|
|||||||
|
|
||||||
Body { "{" statement* "}" }
|
Body { "{" statement* "}" }
|
||||||
|
|
||||||
|
ImportItems { commaSep1NoTrailingComma<ImportItem> }
|
||||||
|
ImportItem { identifier (ImportItemAs identifier)? }
|
||||||
|
|
||||||
expression[@isGroup=Expression] {
|
expression[@isGroup=Expression] {
|
||||||
String |
|
String |
|
||||||
Number |
|
Number |
|
||||||
@ -74,6 +78,8 @@ kw<term> { @specialize[@name={term}]<identifier, term> }
|
|||||||
|
|
||||||
commaSep<term> { (term ("," term)*)? ","? }
|
commaSep<term> { (term ("," term)*)? ","? }
|
||||||
|
|
||||||
|
commaSep1NoTrailingComma<term> { term ("," term)* }
|
||||||
|
|
||||||
@tokens {
|
@tokens {
|
||||||
String[isolate] { "'" ("\\" _ | !['\\])* "'" | '"' ("\\" _ | !["\\])* '"' }
|
String[isolate] { "'" ("\\" _ | !['\\])* "'" | '"' ("\\" _ | !["\\])* '"' }
|
||||||
|
|
||||||
@ -84,7 +90,7 @@ commaSep<term> { (term ("," term)*)? ","? }
|
|||||||
MultOp { "/" | "*" | "\\" }
|
MultOp { "/" | "*" | "\\" }
|
||||||
ExpOp { "^" }
|
ExpOp { "^" }
|
||||||
BangOp { "!" }
|
BangOp { "!" }
|
||||||
CompOp { $[<>] "="? | "!=" | "==" }
|
CompOp { "==" | "!=" | "<=" | ">=" | "<" | ">" }
|
||||||
Equals { "=" }
|
Equals { "=" }
|
||||||
Arrow { "=>" }
|
Arrow { "=>" }
|
||||||
PipeOperator { "|>" }
|
PipeOperator { "|>" }
|
||||||
@ -106,6 +112,9 @@ commaSep<term> { (term ("," term)*)? ","? }
|
|||||||
|
|
||||||
Shebang { "#!" ![\n]* }
|
Shebang { "#!" ![\n]* }
|
||||||
|
|
||||||
|
ImportItemAs { "as" }
|
||||||
|
ImportFrom { "from" }
|
||||||
|
|
||||||
"(" ")"
|
"(" ")"
|
||||||
"{" "}"
|
"{" "}"
|
||||||
"[" "]"
|
"[" "]"
|
||||||
|
@ -12,35 +12,51 @@ type Path = string
|
|||||||
// watcher.addListener(() => { ... }).
|
// watcher.addListener(() => { ... }).
|
||||||
|
|
||||||
export const useFileSystemWatcher = (
|
export const useFileSystemWatcher = (
|
||||||
callback: (path: Path) => Promise<void>,
|
callback: (eventType: string, path: Path) => Promise<void>,
|
||||||
dependencyArray: Path[]
|
paths: Path[]
|
||||||
): void => {
|
): void => {
|
||||||
// Track a ref to the callback. This is how we get the callback updated
|
// Used to track this instance of useFileSystemWatcher.
|
||||||
// across the NodeJS<->Browser boundary.
|
// Assign to ref so it doesn't change between renders.
|
||||||
const callbackRef = useRef<{ fn: (path: Path) => Promise<void> }>({
|
const key = useRef(Math.random().toString())
|
||||||
fn: async (_path) => {},
|
|
||||||
})
|
const [output, setOutput] = useState<
|
||||||
|
{ eventType: string; path: string } | undefined
|
||||||
|
>(undefined)
|
||||||
|
|
||||||
|
// Used to track if paths list changes.
|
||||||
|
const [pathsTracked, setPathsTracked] = useState<Path[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
callbackRef.current.fn = callback
|
if (!output) return
|
||||||
}, [callback])
|
callback(output.eventType, output.path).catch(reportRejection)
|
||||||
|
}, [output])
|
||||||
// Used to track if dependencyArrray changes.
|
|
||||||
const [dependencyArrayTracked, setDependencyArrayTracked] = useState<Path[]>(
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
// On component teardown obliterate all watchers.
|
// On component teardown obliterate all watchers.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// The hook is useless on web.
|
// The hook is useless on web.
|
||||||
if (!isDesktop()) return
|
if (!isDesktop()) return
|
||||||
|
|
||||||
|
const cbWatcher = (eventType: string, path: string) => {
|
||||||
|
setOutput({ eventType, path })
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let path of pathsTracked) {
|
||||||
|
// Because functions don't retain refs between NodeJS-Browser I need to
|
||||||
|
// pass an identifying key so we can later remove it.
|
||||||
|
// A way to think of the function call is:
|
||||||
|
// "For this path, add a new handler with this key"
|
||||||
|
// "There can be many keys (functions) per path"
|
||||||
|
// Again if refs were preserved, we wouldn't need to do this. Keys
|
||||||
|
// gives us uniqueness.
|
||||||
|
window.electron.watchFileOn(path, key.current, cbWatcher)
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
for (let path of dependencyArray) {
|
for (let path of pathsTracked) {
|
||||||
window.electron.watchFileOff(path)
|
window.electron.watchFileOff(path, key.current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [pathsTracked])
|
||||||
|
|
||||||
function difference<T>(l1: T[], l2: T[]): [T[], T[]] {
|
function difference<T>(l1: T[], l2: T[]): [T[], T[]] {
|
||||||
return [
|
return [
|
||||||
@ -49,8 +65,7 @@ export const useFileSystemWatcher = (
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasDiff =
|
const hasDiff = difference(paths, pathsTracked)[0].length !== 0
|
||||||
difference(dependencyArray, dependencyArrayTracked)[0].length !== 0
|
|
||||||
|
|
||||||
// Removing 1 watcher at a time is only possible because in a filesystem,
|
// Removing 1 watcher at a time is only possible because in a filesystem,
|
||||||
// a path is unique (there can never be two paths with the same name).
|
// a path is unique (there can never be two paths with the same name).
|
||||||
@ -61,19 +76,8 @@ export const useFileSystemWatcher = (
|
|||||||
|
|
||||||
if (!hasDiff) return
|
if (!hasDiff) return
|
||||||
|
|
||||||
const [pathsRemoved, pathsRemaining] = difference(
|
const [, pathsRemaining] = difference(pathsTracked, paths)
|
||||||
dependencyArrayTracked,
|
const [pathsAdded] = difference(paths, pathsTracked)
|
||||||
dependencyArray
|
setPathsTracked(pathsRemaining.concat(pathsAdded))
|
||||||
)
|
|
||||||
for (let path of pathsRemoved) {
|
|
||||||
window.electron.watchFileOff(path)
|
|
||||||
}
|
|
||||||
const [pathsAdded] = difference(dependencyArray, dependencyArrayTracked)
|
|
||||||
for (let path of pathsAdded) {
|
|
||||||
window.electron.watchFileOn(path, (_eventType: string, path: Path) => {
|
|
||||||
callbackRef.current.fn(path).catch(reportRejection)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setDependencyArrayTracked(pathsRemaining.concat(pathsAdded))
|
|
||||||
}, [hasDiff])
|
}, [hasDiff])
|
||||||
}
|
}
|
||||||
|
@ -293,6 +293,24 @@ code {
|
|||||||
which lets you use them with @apply in your CSS, and get
|
which lets you use them with @apply in your CSS, and get
|
||||||
autocomplete in classNames in your JSX.
|
autocomplete in classNames in your JSX.
|
||||||
*/
|
*/
|
||||||
|
.parsed-markdown ul,
|
||||||
|
.parsed-markdown ol {
|
||||||
|
@apply list-outside pl-4 lg:pl-8 my-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parsed-markdown ul li {
|
||||||
|
@apply list-disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parsed-markdown li p {
|
||||||
|
@apply inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parsed-markdown code {
|
||||||
|
@apply px-1 py-0.5 rounded-sm;
|
||||||
|
@apply bg-chalkboard-20 text-chalkboard-80;
|
||||||
|
@apply dark:bg-chalkboard-80 dark:text-chalkboard-30;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#code-mirror-override .cm-scroller,
|
#code-mirror-override .cm-scroller,
|
||||||
|
@ -70,15 +70,17 @@ if (isDesktop()) {
|
|||||||
id: AUTO_UPDATER_TOAST_ID,
|
id: AUTO_UPDATER_TOAST_ID,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
window.electron.onUpdateDownloaded((version: string) => {
|
window.electron.onUpdateDownloaded(({ version, releaseNotes }) => {
|
||||||
const message = `A new update (${version}) was downloaded and will be available next time you open the app.`
|
const message = `A new update (${version}) was downloaded and will be available next time you open the app.`
|
||||||
console.log(message)
|
console.log(message)
|
||||||
toast.custom(
|
toast.custom(
|
||||||
ToastUpdate({
|
ToastUpdate({
|
||||||
version,
|
version,
|
||||||
|
releaseNotes,
|
||||||
onRestart: () => {
|
onRestart: () => {
|
||||||
window.electron.appRestart()
|
window.electron.appRestart()
|
||||||
},
|
},
|
||||||
|
onDismiss: () => {},
|
||||||
}),
|
}),
|
||||||
{ duration: 30000, id: AUTO_UPDATER_TOAST_ID }
|
{ duration: 30000, id: AUTO_UPDATER_TOAST_ID }
|
||||||
)
|
)
|
||||||
|
@ -40,9 +40,7 @@ export class KclManager {
|
|||||||
nonCodeMeta: {
|
nonCodeMeta: {
|
||||||
nonCodeNodes: {},
|
nonCodeNodes: {},
|
||||||
start: [],
|
start: [],
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
digest: null,
|
|
||||||
}
|
}
|
||||||
private _execState: ExecState = emptyExecState()
|
private _execState: ExecState = emptyExecState()
|
||||||
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
||||||
@ -208,9 +206,7 @@ export class KclManager {
|
|||||||
nonCodeMeta: {
|
nonCodeMeta: {
|
||||||
nonCodeNodes: {},
|
nonCodeNodes: {},
|
||||||
start: [],
|
start: [],
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
digest: null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +172,6 @@ const sk2 = startSketchOn('XY')
|
|||||||
start: 114,
|
start: 114,
|
||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
value: 'p',
|
value: 'p',
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
sourceRange: [95, 117],
|
sourceRange: [95, 117],
|
||||||
@ -223,7 +222,6 @@ const sk2 = startSketchOn('XY')
|
|||||||
start: 114,
|
start: 114,
|
||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
value: 'p',
|
value: 'p',
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
@ -266,7 +264,6 @@ const sk2 = startSketchOn('XY')
|
|||||||
start: 417,
|
start: 417,
|
||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
value: 'o',
|
value: 'o',
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
sourceRange: [399, 420],
|
sourceRange: [399, 420],
|
||||||
@ -317,7 +314,6 @@ const sk2 = startSketchOn('XY')
|
|||||||
start: 417,
|
start: 417,
|
||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
value: 'o',
|
value: 'o',
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
@ -73,7 +73,6 @@ const newVar = myVar + 1`
|
|||||||
start: 89,
|
start: 89,
|
||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
value: 'myPath',
|
value: 'myPath',
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -99,7 +98,6 @@ const newVar = myVar + 1`
|
|||||||
start: 143,
|
start: 143,
|
||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
value: 'rightPath',
|
value: 'rightPath',
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@ -201,7 +199,6 @@ const newVar = myVar + 1`
|
|||||||
start: 109,
|
start: 109,
|
||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
value: 'myPath',
|
value: 'myPath',
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -100,15 +100,15 @@ describe('Testing findUniqueName', () => {
|
|||||||
it('should find a unique name', () => {
|
it('should find a unique name', () => {
|
||||||
const result = findUniqueName(
|
const result = findUniqueName(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
{ type: 'Identifier', name: 'yo01', start: 0, end: 0, digest: null },
|
{ type: 'Identifier', name: 'yo01', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo02', start: 0, end: 0, digest: null },
|
{ type: 'Identifier', name: 'yo02', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo03', start: 0, end: 0, digest: null },
|
{ type: 'Identifier', name: 'yo03', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo04', start: 0, end: 0, digest: null },
|
{ type: 'Identifier', name: 'yo04', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo05', start: 0, end: 0, digest: null },
|
{ type: 'Identifier', name: 'yo05', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo06', start: 0, end: 0, digest: null },
|
{ type: 'Identifier', name: 'yo06', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo07', start: 0, end: 0, digest: null },
|
{ type: 'Identifier', name: 'yo07', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo08', start: 0, end: 0, digest: null },
|
{ type: 'Identifier', name: 'yo08', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo09', start: 0, end: 0, digest: null },
|
{ type: 'Identifier', name: 'yo09', start: 0, end: 0 },
|
||||||
] satisfies Identifier[]),
|
] satisfies Identifier[]),
|
||||||
'yo',
|
'yo',
|
||||||
2
|
2
|
||||||
@ -123,8 +123,7 @@ describe('Testing addSketchTo', () => {
|
|||||||
body: [],
|
body: [],
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
nonCodeMeta: { nonCodeNodes: {}, start: [], digest: null },
|
nonCodeMeta: { nonCodeNodes: {}, start: [] },
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
'yz'
|
'yz'
|
||||||
)
|
)
|
||||||
|
@ -241,7 +241,6 @@ export function mutateObjExpProp(
|
|||||||
value: updateWith,
|
value: updateWith,
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -501,6 +500,7 @@ export function sketchOnExtrudedFace(
|
|||||||
createIdentifier(extrudeName ? extrudeName : oldSketchName),
|
createIdentifier(extrudeName ? extrudeName : oldSketchName),
|
||||||
_tag,
|
_tag,
|
||||||
]),
|
]),
|
||||||
|
undefined,
|
||||||
'const'
|
'const'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -578,7 +578,6 @@ export function createLiteral(value: string | number): Literal {
|
|||||||
end: 0,
|
end: 0,
|
||||||
value,
|
value,
|
||||||
raw: `${value}`,
|
raw: `${value}`,
|
||||||
digest: null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,7 +586,7 @@ export function createTagDeclarator(value: string): TagDeclarator {
|
|||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
value,
|
value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -597,7 +596,7 @@ export function createIdentifier(name: string): Identifier {
|
|||||||
type: 'Identifier',
|
type: 'Identifier',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
name,
|
name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -607,7 +606,6 @@ export function createPipeSubstitution(): PipeSubstitution {
|
|||||||
type: 'PipeSubstitution',
|
type: 'PipeSubstitution',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -623,12 +621,11 @@ export function createCallExpressionStdLib(
|
|||||||
type: 'Identifier',
|
type: 'Identifier',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
optional: false,
|
optional: false,
|
||||||
arguments: args,
|
arguments: args,
|
||||||
digest: null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -644,12 +641,11 @@ export function createCallExpression(
|
|||||||
type: 'Identifier',
|
type: 'Identifier',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
optional: false,
|
optional: false,
|
||||||
arguments: args,
|
arguments: args,
|
||||||
digest: null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,7 +656,7 @@ export function createArrayExpression(
|
|||||||
type: 'ArrayExpression',
|
type: 'ArrayExpression',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
nonCodeMeta: nonCodeMetaEmpty(),
|
nonCodeMeta: nonCodeMetaEmpty(),
|
||||||
elements,
|
elements,
|
||||||
}
|
}
|
||||||
@ -673,7 +669,7 @@ export function createPipeExpression(
|
|||||||
type: 'PipeExpression',
|
type: 'PipeExpression',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
body,
|
body,
|
||||||
nonCodeMeta: nonCodeMetaEmpty(),
|
nonCodeMeta: nonCodeMetaEmpty(),
|
||||||
}
|
}
|
||||||
@ -682,23 +678,25 @@ export function createPipeExpression(
|
|||||||
export function createVariableDeclaration(
|
export function createVariableDeclaration(
|
||||||
varName: string,
|
varName: string,
|
||||||
init: VariableDeclarator['init'],
|
init: VariableDeclarator['init'],
|
||||||
|
visibility: VariableDeclaration['visibility'] = 'default',
|
||||||
kind: VariableDeclaration['kind'] = 'const'
|
kind: VariableDeclaration['kind'] = 'const'
|
||||||
): VariableDeclaration {
|
): VariableDeclaration {
|
||||||
return {
|
return {
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
declarations: [
|
declarations: [
|
||||||
{
|
{
|
||||||
type: 'VariableDeclarator',
|
type: 'VariableDeclarator',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
id: createIdentifier(varName),
|
id: createIdentifier(varName),
|
||||||
init,
|
init,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
visibility,
|
||||||
kind,
|
kind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -710,14 +708,14 @@ export function createObjectExpression(properties: {
|
|||||||
type: 'ObjectExpression',
|
type: 'ObjectExpression',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
nonCodeMeta: nonCodeMetaEmpty(),
|
nonCodeMeta: nonCodeMetaEmpty(),
|
||||||
properties: Object.entries(properties).map(([key, value]) => ({
|
properties: Object.entries(properties).map(([key, value]) => ({
|
||||||
type: 'ObjectProperty',
|
type: 'ObjectProperty',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
key: createIdentifier(key),
|
key: createIdentifier(key),
|
||||||
digest: null,
|
|
||||||
value,
|
value,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
@ -731,7 +729,7 @@ export function createUnaryExpression(
|
|||||||
type: 'UnaryExpression',
|
type: 'UnaryExpression',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
operator,
|
operator,
|
||||||
argument,
|
argument,
|
||||||
}
|
}
|
||||||
@ -746,7 +744,7 @@ export function createBinaryExpression([left, operator, right]: [
|
|||||||
type: 'BinaryExpression',
|
type: 'BinaryExpression',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
digest: null,
|
|
||||||
operator,
|
operator,
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
@ -1136,5 +1134,5 @@ export async function deleteFromSelection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nonCodeMetaEmpty = () => {
|
const nonCodeMetaEmpty = () => {
|
||||||
return { nonCodeNodes: {}, start: [], digest: null }
|
return { nonCodeNodes: {}, start: [] }
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ beforeAll(async () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, 20_000)
|
}, 30_000)
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
engineCommandManager.tearDown()
|
engineCommandManager.tearDown()
|
||||||
@ -620,7 +620,7 @@ describe('Testing button states', () => {
|
|||||||
it('should return true when body exists and segment is selected', async () => {
|
it('should return true when body exists and segment is selected', async () => {
|
||||||
await runButtonStateTest(codeWithBody, `line([10, 0], %)`, true)
|
await runButtonStateTest(codeWithBody, `line([10, 0], %)`, true)
|
||||||
})
|
})
|
||||||
it('hould return false when body exists and not a segment is selected', async () => {
|
it('should return false when body exists and not a segment is selected', async () => {
|
||||||
await runButtonStateTest(codeWithBody, `close(%)`, false)
|
await runButtonStateTest(codeWithBody, `close(%)`, false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
CallExpression,
|
CallExpression,
|
||||||
|
Expr,
|
||||||
|
Identifier,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
Program,
|
Program,
|
||||||
@ -27,7 +29,7 @@ import {
|
|||||||
sketchLineHelperMap,
|
sketchLineHelperMap,
|
||||||
} from '../std/sketch'
|
} from '../std/sketch'
|
||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
import { Selections, canFilletSelection } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { KclCommandValue } from 'lib/commandTypes'
|
import { KclCommandValue } from 'lib/commandTypes'
|
||||||
import {
|
import {
|
||||||
ArtifactGraph,
|
ArtifactGraph,
|
||||||
@ -66,7 +68,10 @@ export function modifyAstCloneWithFilletAndTag(
|
|||||||
const artifactGraph = engineCommandManager.artifactGraph
|
const artifactGraph = engineCommandManager.artifactGraph
|
||||||
|
|
||||||
// Step 1: modify ast with tags and group them by extrude nodes (bodies)
|
// Step 1: modify ast with tags and group them by extrude nodes (bodies)
|
||||||
const extrudeToTagsMap: Map<PathToNode, string[]> = new Map()
|
const extrudeToTagsMap: Map<
|
||||||
|
PathToNode,
|
||||||
|
Array<{ tag: string; selectionType: string }>
|
||||||
|
> = new Map()
|
||||||
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
|
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
|
||||||
|
|
||||||
for (const selectionRange of selection.codeBasedSelections) {
|
for (const selectionRange of selection.codeBasedSelections) {
|
||||||
@ -74,6 +79,7 @@ export function modifyAstCloneWithFilletAndTag(
|
|||||||
codeBasedSelections: [selectionRange],
|
codeBasedSelections: [selectionRange],
|
||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
}
|
}
|
||||||
|
const selectionType = singleSelection.codeBasedSelections[0].type
|
||||||
|
|
||||||
const result = getPathToExtrudeForSegmentSelection(
|
const result = getPathToExtrudeForSegmentSelection(
|
||||||
clonedAstForGetExtrude,
|
clonedAstForGetExtrude,
|
||||||
@ -89,6 +95,7 @@ export function modifyAstCloneWithFilletAndTag(
|
|||||||
)
|
)
|
||||||
if (err(tagResult)) return tagResult
|
if (err(tagResult)) return tagResult
|
||||||
const { tag } = tagResult
|
const { tag } = tagResult
|
||||||
|
const tagInfo = { tag, selectionType }
|
||||||
|
|
||||||
// Group tags by their corresponding extrude node
|
// Group tags by their corresponding extrude node
|
||||||
const extrudeKey = JSON.stringify(pathToExtrudeNode)
|
const extrudeKey = JSON.stringify(pathToExtrudeNode)
|
||||||
@ -96,23 +103,29 @@ export function modifyAstCloneWithFilletAndTag(
|
|||||||
if (lookupMap.has(extrudeKey)) {
|
if (lookupMap.has(extrudeKey)) {
|
||||||
const existingPath = lookupMap.get(extrudeKey)
|
const existingPath = lookupMap.get(extrudeKey)
|
||||||
if (!existingPath) return new Error('Path to extrude node not found.')
|
if (!existingPath) return new Error('Path to extrude node not found.')
|
||||||
extrudeToTagsMap.get(existingPath)?.push(tag)
|
extrudeToTagsMap.get(existingPath)?.push(tagInfo)
|
||||||
} else {
|
} else {
|
||||||
lookupMap.set(extrudeKey, pathToExtrudeNode)
|
lookupMap.set(extrudeKey, pathToExtrudeNode)
|
||||||
extrudeToTagsMap.set(pathToExtrudeNode, [tag])
|
extrudeToTagsMap.set(pathToExtrudeNode, [tagInfo])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Apply fillet(s) for each extrude node (body)
|
// Step 2: Apply fillet(s) for each extrude node (body)
|
||||||
let pathToFilletNodes: Array<PathToNode> = []
|
let pathToFilletNodes: Array<PathToNode> = []
|
||||||
for (const [pathToExtrudeNode, tags] of extrudeToTagsMap.entries()) {
|
for (const [pathToExtrudeNode, tagInfos] of extrudeToTagsMap.entries()) {
|
||||||
// Create a fillet expression with multiple tags
|
// Create a fillet expression with multiple tags
|
||||||
const radiusValue =
|
const radiusValue =
|
||||||
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
|
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
|
||||||
|
|
||||||
|
const tagCalls = tagInfos.map(({ tag, selectionType }) => {
|
||||||
|
return getEdgeTagCall(tag, selectionType)
|
||||||
|
})
|
||||||
|
const firstTag = tagCalls[0] // can be Identifier or CallExpression (for opposite and adjacent edges)
|
||||||
|
|
||||||
const filletCall = createCallExpressionStdLib('fillet', [
|
const filletCall = createCallExpressionStdLib('fillet', [
|
||||||
createObjectExpression({
|
createObjectExpression({
|
||||||
radius: radiusValue,
|
radius: radiusValue,
|
||||||
tags: createArrayExpression(tags.map((tag) => createIdentifier(tag))),
|
tags: createArrayExpression(tagCalls),
|
||||||
}),
|
}),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
@ -144,7 +157,7 @@ export function modifyAstCloneWithFilletAndTag(
|
|||||||
pathToFilletNode = getPathToNodeOfFilletLiteral(
|
pathToFilletNode = getPathToNodeOfFilletLiteral(
|
||||||
pathToExtrudeNode,
|
pathToExtrudeNode,
|
||||||
extrudeDeclarator,
|
extrudeDeclarator,
|
||||||
tags[0]
|
firstTag
|
||||||
)
|
)
|
||||||
pathToFilletNodes.push(pathToFilletNode)
|
pathToFilletNodes.push(pathToFilletNode)
|
||||||
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
|
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
|
||||||
@ -165,7 +178,7 @@ export function modifyAstCloneWithFilletAndTag(
|
|||||||
pathToFilletNode = getPathToNodeOfFilletLiteral(
|
pathToFilletNode = getPathToNodeOfFilletLiteral(
|
||||||
pathToExtrudeNode,
|
pathToExtrudeNode,
|
||||||
extrudeDeclarator,
|
extrudeDeclarator,
|
||||||
tags[0]
|
firstTag
|
||||||
)
|
)
|
||||||
pathToFilletNodes.push(pathToFilletNode)
|
pathToFilletNodes.push(pathToFilletNode)
|
||||||
} else {
|
} else {
|
||||||
@ -276,6 +289,21 @@ function mutateAstWithTagForSketchSegment(
|
|||||||
return { modifiedAst: astClone, tag }
|
return { modifiedAst: astClone, tag }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getEdgeTagCall(
|
||||||
|
tag: string,
|
||||||
|
selectionType: string
|
||||||
|
): Identifier | CallExpression {
|
||||||
|
let tagCall: Expr = createIdentifier(tag)
|
||||||
|
|
||||||
|
// Modify the tag based on selectionType
|
||||||
|
if (selectionType === 'edge') {
|
||||||
|
tagCall = createCallExpressionStdLib('getOppositeEdge', [tagCall])
|
||||||
|
} else if (selectionType === 'adjacent-edge') {
|
||||||
|
tagCall = createCallExpressionStdLib('getNextAdjacentEdge', [tagCall])
|
||||||
|
}
|
||||||
|
return tagCall
|
||||||
|
}
|
||||||
|
|
||||||
function locateExtrudeDeclarator(
|
function locateExtrudeDeclarator(
|
||||||
node: Program,
|
node: Program,
|
||||||
pathToExtrudeNode: PathToNode
|
pathToExtrudeNode: PathToNode
|
||||||
@ -311,7 +339,7 @@ function locateExtrudeDeclarator(
|
|||||||
function getPathToNodeOfFilletLiteral(
|
function getPathToNodeOfFilletLiteral(
|
||||||
pathToExtrudeNode: PathToNode,
|
pathToExtrudeNode: PathToNode,
|
||||||
extrudeDeclarator: VariableDeclarator,
|
extrudeDeclarator: VariableDeclarator,
|
||||||
tag: string
|
tag: Identifier | CallExpression
|
||||||
): PathToNode {
|
): PathToNode {
|
||||||
let pathToFilletObj: PathToNode = []
|
let pathToFilletObj: PathToNode = []
|
||||||
let inFillet = false
|
let inFillet = false
|
||||||
@ -347,12 +375,30 @@ function getPathToNodeOfFilletLiteral(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasTag(node: ObjectExpression, tag: string): boolean {
|
function hasTag(
|
||||||
|
node: ObjectExpression,
|
||||||
|
tag: Identifier | CallExpression
|
||||||
|
): boolean {
|
||||||
return node.properties.some((prop) => {
|
return node.properties.some((prop) => {
|
||||||
if (prop.key.name === 'tags' && prop.value.type === 'ArrayExpression') {
|
if (prop.key.name === 'tags' && prop.value.type === 'ArrayExpression') {
|
||||||
return prop.value.elements.some(
|
// if selection is a base edge:
|
||||||
(element) => element.type === 'Identifier' && element.name === tag
|
if (tag.type === 'Identifier') {
|
||||||
)
|
return prop.value.elements.some(
|
||||||
|
(element) =>
|
||||||
|
element.type === 'Identifier' && element.name === tag.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// if selection is an adjacent or opposite edge:
|
||||||
|
if (tag.type === 'CallExpression') {
|
||||||
|
return prop.value.elements.some(
|
||||||
|
(element) =>
|
||||||
|
element.type === 'CallExpression' &&
|
||||||
|
element.callee.name === tag.callee.name && // edge location
|
||||||
|
element.arguments[0].type === 'Identifier' &&
|
||||||
|
tag.arguments[0].type === 'Identifier' &&
|
||||||
|
element.arguments[0].name === tag.arguments[0].name // tag name
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
@ -383,7 +429,7 @@ export const hasValidFilletSelection = ({
|
|||||||
ast: Program
|
ast: Program
|
||||||
code: string
|
code: string
|
||||||
}) => {
|
}) => {
|
||||||
// case 0: check if there is anything filletable in the scene
|
// check if there is anything filletable in the scene
|
||||||
let extrudeExists = false
|
let extrudeExists = false
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
enter(node) {
|
enter(node) {
|
||||||
@ -394,65 +440,88 @@ export const hasValidFilletSelection = ({
|
|||||||
})
|
})
|
||||||
if (!extrudeExists) return false
|
if (!extrudeExists) return false
|
||||||
|
|
||||||
// case 1: nothing selected, test whether the extrusion exists
|
// check if nothing is selected
|
||||||
if (selectionRanges) {
|
if (selectionRanges.codeBasedSelections.length === 0) {
|
||||||
if (selectionRanges.codeBasedSelections.length === 0) {
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
const range0 = selectionRanges.codeBasedSelections[0].range[0]
|
|
||||||
const codeLength = code.length
|
|
||||||
if (range0 === codeLength) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 2: sketch segment selected, test whether it is extruded
|
// check if selection is last string in code
|
||||||
// TODO: add loft / sweep check
|
if (selectionRanges.codeBasedSelections[0].range[0] === code.length) {
|
||||||
if (selectionRanges.codeBasedSelections.length > 0) {
|
return true
|
||||||
const isExtruded = hasSketchPipeBeenExtruded(
|
}
|
||||||
selectionRanges.codeBasedSelections[0],
|
|
||||||
ast
|
// selection exists:
|
||||||
|
for (const selection of selectionRanges.codeBasedSelections) {
|
||||||
|
// check if all selections are in sketchLineHelperMap
|
||||||
|
const path = getNodePathFromSourceRange(ast, selection.range)
|
||||||
|
const segmentNode = getNodeFromPath<CallExpression>(
|
||||||
|
ast,
|
||||||
|
path,
|
||||||
|
'CallExpression'
|
||||||
)
|
)
|
||||||
if (isExtruded) {
|
if (err(segmentNode)) return false
|
||||||
const pathToSelectedNode = getNodePathFromSourceRange(
|
if (segmentNode.node.type !== 'CallExpression') {
|
||||||
ast,
|
return false
|
||||||
selectionRanges.codeBasedSelections[0].range
|
}
|
||||||
)
|
if (!(segmentNode.node.callee.name in sketchLineHelperMap)) {
|
||||||
const segmentNode = getNodeFromPath<CallExpression>(
|
|
||||||
ast,
|
|
||||||
pathToSelectedNode,
|
|
||||||
'CallExpression'
|
|
||||||
)
|
|
||||||
if (err(segmentNode)) return false
|
|
||||||
if (segmentNode.node.type === 'CallExpression') {
|
|
||||||
const segmentName = segmentNode.node.callee.name
|
|
||||||
if (segmentName in sketchLineHelperMap) {
|
|
||||||
// Add check whether the tag exists at all:
|
|
||||||
if (!(segmentNode.node.arguments.length === 3)) return true
|
|
||||||
// If the tag exists, check if it is already filleted
|
|
||||||
const edges = isTagUsedInFillet({
|
|
||||||
ast,
|
|
||||||
callExp: segmentNode.node,
|
|
||||||
})
|
|
||||||
// edge has already been filleted
|
|
||||||
if (
|
|
||||||
['edge', 'default'].includes(
|
|
||||||
selectionRanges.codeBasedSelections[0].type
|
|
||||||
) &&
|
|
||||||
edges.includes('baseEdge')
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return canFilletSelection(selectionRanges)
|
// check if selection is extruded
|
||||||
|
// TODO: option 1 : extrude is in the sketch pipe
|
||||||
|
|
||||||
|
// option 2: extrude is outside the sketch pipe
|
||||||
|
const extrudeExists = hasSketchPipeBeenExtruded(selection, ast)
|
||||||
|
if (err(extrudeExists)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!extrudeExists) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if tag exists for the selection
|
||||||
|
let tagExists = false
|
||||||
|
let tag = ''
|
||||||
|
traverse(segmentNode.node, {
|
||||||
|
enter(node) {
|
||||||
|
if (node.type === 'TagDeclarator') {
|
||||||
|
tagExists = true
|
||||||
|
tag = node.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// check if tag is used in fillet
|
||||||
|
if (tagExists) {
|
||||||
|
// create tag call
|
||||||
|
let tagCall: Expr = getEdgeTagCall(tag, selection.type)
|
||||||
|
|
||||||
|
// check if tag is used in fillet
|
||||||
|
let inFillet = false
|
||||||
|
let tagUsedInFillet = false
|
||||||
|
traverse(ast, {
|
||||||
|
enter(node) {
|
||||||
|
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||||
|
inFillet = true
|
||||||
|
}
|
||||||
|
if (inFillet && node.type === 'ObjectExpression') {
|
||||||
|
if (hasTag(node, tagCall)) {
|
||||||
|
tagUsedInFillet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leave(node) {
|
||||||
|
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||||
|
inFillet = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (tagUsedInFillet) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type EdgeTypes =
|
type EdgeTypes =
|
||||||
|
@ -28,6 +28,7 @@ import {
|
|||||||
getConstraintType,
|
getConstraintType,
|
||||||
} from './std/sketchcombos'
|
} from './std/sketchcombos'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
||||||
@ -120,7 +121,12 @@ export function getNodeFromPathCurry(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function moreNodePathFromSourceRange(
|
function moreNodePathFromSourceRange(
|
||||||
node: Expr | ExpressionStatement | VariableDeclaration | ReturnStatement,
|
node:
|
||||||
|
| Expr
|
||||||
|
| ImportStatement
|
||||||
|
| ExpressionStatement
|
||||||
|
| VariableDeclaration
|
||||||
|
| ReturnStatement,
|
||||||
sourceRange: Selection['range'],
|
sourceRange: Selection['range'],
|
||||||
previousPath: PathToNode = [['body', '']]
|
previousPath: PathToNode = [['body', '']]
|
||||||
): PathToNode {
|
): PathToNode {
|
||||||
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 378 KiB |
Before Width: | Height: | Size: 577 KiB After Width: | Height: | Size: 613 KiB |
@ -18,7 +18,7 @@ class FileSystemManager {
|
|||||||
return Promise.resolve(window.electron.path.join(dir, path))
|
return Promise.resolve(window.electron.path.join(dir, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
async readFile(path: string): Promise<Uint8Array | void> {
|
async readFile(path: string): Promise<Uint8Array> {
|
||||||
// Using local file system only works from desktop.
|
// Using local file system only works from desktop.
|
||||||
if (!isDesktop()) {
|
if (!isDesktop()) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
|
@ -1823,11 +1823,10 @@ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
body: [],
|
body: [],
|
||||||
digest: null,
|
|
||||||
nonCodeMeta: {
|
nonCodeMeta: {
|
||||||
start: [],
|
start: [],
|
||||||
nonCodeNodes: [],
|
nonCodeNodes: [],
|
||||||
digest: null,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pathToNode,
|
pathToNode,
|
||||||
|
@ -426,6 +426,7 @@ export const _executor = async (
|
|||||||
baseUnit,
|
baseUnit,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
fileSystemManager,
|
fileSystemManager,
|
||||||
|
undefined,
|
||||||
isMock
|
isMock
|
||||||
)
|
)
|
||||||
return execStateFromRaw(execState)
|
return execStateFromRaw(execState)
|
||||||
|
3
src/lib/codeEditor.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const normalizeLineEndings = (str: string, normalized = '\n') => {
|
||||||
|
return str.replace(/\r?\n/g, normalized)
|
||||||
|
}
|
@ -190,10 +190,31 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
options: () => {
|
options: () => {
|
||||||
return Object.entries(machineManager.machines).map(
|
return Object.entries(machineManager.machines).map(
|
||||||
([_, machine]) => ({
|
([_, machine]) => ({
|
||||||
name: `${machine.id} (${
|
name:
|
||||||
machine.make_model.model || machine.make_model.manufacturer
|
`${machine.id} (${
|
||||||
}) via ${machineManager.machineApiIp || 'the local network'}`,
|
machine.make_model.model || machine.make_model.manufacturer
|
||||||
|
}) (${machine.state.state})` +
|
||||||
|
(machine.hardware_configuration &&
|
||||||
|
machine.hardware_configuration.type !== 'none' &&
|
||||||
|
machine.hardware_configuration.config.nozzle_diameter
|
||||||
|
? ` - Nozzle Diameter: ${machine.hardware_configuration.config.nozzle_diameter}`
|
||||||
|
: '') +
|
||||||
|
(machine.hardware_configuration &&
|
||||||
|
machine.hardware_configuration.type !== 'none' &&
|
||||||
|
machine.hardware_configuration.config.filaments &&
|
||||||
|
machine.hardware_configuration.config.filaments[0]
|
||||||
|
? ` - ${
|
||||||
|
machine.hardware_configuration.config.filaments[0].name
|
||||||
|
} #${
|
||||||
|
machine.hardware_configuration.config &&
|
||||||
|
machine.hardware_configuration.config.filaments[0].color?.slice(
|
||||||
|
0,
|
||||||
|
6
|
||||||
|
)
|
||||||
|
}`
|
||||||
|
: ''),
|
||||||
isCurrent: false,
|
isCurrent: false,
|
||||||
|
disabled: machine.state.state !== 'idle',
|
||||||
value: machine as components['schemas']['MachineInfoResponse'],
|
value: machine as components['schemas']['MachineInfoResponse'],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -258,5 +258,6 @@ export type CommandArgumentWithName<
|
|||||||
export type CommandArgumentOption<A> = {
|
export type CommandArgumentOption<A> = {
|
||||||
name: string
|
name: string
|
||||||
isCurrent?: boolean
|
isCurrent?: boolean
|
||||||
|
disabled?: boolean
|
||||||
value: A
|
value: A
|
||||||
}
|
}
|
||||||
|
@ -448,7 +448,9 @@ export const readProjectSettingsFile = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const configToml = await window.electron.readFile(settingsPath)
|
const configToml = await window.electron.readFile(settingsPath, {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
})
|
||||||
const configObj = parseProjectSettings(configToml)
|
const configObj = parseProjectSettings(configToml)
|
||||||
if (err(configObj)) {
|
if (err(configObj)) {
|
||||||
return Promise.reject(configObj)
|
return Promise.reject(configObj)
|
||||||
@ -467,7 +469,9 @@ export const readAppSettingsFile = async () => {
|
|||||||
|
|
||||||
// The file exists, read it and parse it.
|
// The file exists, read it and parse it.
|
||||||
if (window.electron.exists(settingsPath)) {
|
if (window.electron.exists(settingsPath)) {
|
||||||
const configToml = await window.electron.readFile(settingsPath)
|
const configToml = await window.electron.readFile(settingsPath, {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
})
|
||||||
const parsedAppConfig = parseAppSettings(configToml)
|
const parsedAppConfig = parseAppSettings(configToml)
|
||||||
if (err(parsedAppConfig)) {
|
if (err(parsedAppConfig)) {
|
||||||
return Promise.reject(parsedAppConfig)
|
return Promise.reject(parsedAppConfig)
|
||||||
@ -527,7 +531,9 @@ export const readTokenFile = async () => {
|
|||||||
let settingsPath = await getTokenFilePath()
|
let settingsPath = await getTokenFilePath()
|
||||||
|
|
||||||
if (window.electron.exists(settingsPath)) {
|
if (window.electron.exists(settingsPath)) {
|
||||||
const token: string = await window.electron.readFile(settingsPath)
|
const token: string = await window.electron.readFile(settingsPath, {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
})
|
||||||
if (!token) return ''
|
if (!token) return ''
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
216
src/lib/machine-api.d.ts
vendored
@ -55,6 +55,23 @@ export interface paths {
|
|||||||
patch?: never
|
patch?: never
|
||||||
trace?: never
|
trace?: never
|
||||||
}
|
}
|
||||||
|
'/metrics': {
|
||||||
|
parameters: {
|
||||||
|
query?: never
|
||||||
|
header?: never
|
||||||
|
path?: never
|
||||||
|
cookie?: never
|
||||||
|
}
|
||||||
|
/** List available machines and their statuses */
|
||||||
|
get: operations['get_metrics']
|
||||||
|
put?: never
|
||||||
|
post?: never
|
||||||
|
delete?: never
|
||||||
|
options?: never
|
||||||
|
head?: never
|
||||||
|
patch?: never
|
||||||
|
trace?: never
|
||||||
|
}
|
||||||
'/ping': {
|
'/ping': {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never
|
query?: never
|
||||||
@ -102,18 +119,96 @@ export interface components {
|
|||||||
/** @description Extra machine-specific information regarding a connected machine. */
|
/** @description Extra machine-specific information regarding a connected machine. */
|
||||||
ExtraMachineInfoResponse:
|
ExtraMachineInfoResponse:
|
||||||
| {
|
| {
|
||||||
Moonraker: Record<string, never>
|
/** @enum {string} */
|
||||||
|
type: 'moonraker'
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
Usb: Record<string, never>
|
/** @enum {string} */
|
||||||
|
type: 'usb'
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
Bambu: Record<string, never>
|
/** @description The current stage of the machine as defined by Bambu which can include errors, etc. */
|
||||||
|
current_stage?: components['schemas']['Stage'] | null
|
||||||
|
/** @description The nozzle diameter of the machine. */
|
||||||
|
nozzle_diameter: components['schemas']['NozzleDiameter']
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'bambu'
|
||||||
|
}
|
||||||
|
/** @description Configuration for a FDM-based printer. */
|
||||||
|
FdmHardwareConfiguration: {
|
||||||
|
/** @description The filaments the printer has access to. */
|
||||||
|
filaments: components['schemas']['Filament'][]
|
||||||
|
/**
|
||||||
|
* Format: double
|
||||||
|
* @description Diameter of the extrusion nozzle, in mm.
|
||||||
|
*/
|
||||||
|
nozzle_diameter: number
|
||||||
|
}
|
||||||
|
/** @description Information about the filament being used in a FDM printer. */
|
||||||
|
Filament: {
|
||||||
|
/** @description The color (as hex without the `#`) of the filament, this is likely specific to the manufacturer. */
|
||||||
|
color?: string | null
|
||||||
|
/** @description The material that the filament is made of. */
|
||||||
|
material: components['schemas']['FilamentMaterial']
|
||||||
|
/** @description The name of the filament, this is likely specfic to the manufacturer. */
|
||||||
|
name?: string | null
|
||||||
|
}
|
||||||
|
/** @description The material that the filament is made of. */
|
||||||
|
FilamentMaterial:
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'pla'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'pla_support'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'abs'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'petg'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'nylon'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'tpu'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'pva'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'hips'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'composite'
|
||||||
|
}
|
||||||
|
/** @description The hardware configuration of a machine. */
|
||||||
|
HardwareConfiguration:
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'none'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @description The configuration for the FDM printer. */
|
||||||
|
config: components['schemas']['FdmHardwareConfiguration']
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'fdm'
|
||||||
}
|
}
|
||||||
/** @description Information regarding a connected machine. */
|
/** @description Information regarding a connected machine. */
|
||||||
MachineInfoResponse: {
|
MachineInfoResponse: {
|
||||||
/** @description Additional, per-machine information which is specific to the underlying machine type. */
|
/** @description Additional, per-machine information which is specific to the underlying machine type. */
|
||||||
extra?: components['schemas']['ExtraMachineInfoResponse'] | null
|
extra?: components['schemas']['ExtraMachineInfoResponse'] | null
|
||||||
|
/** @description Information about how the Machine is currently configured. */
|
||||||
|
hardware_configuration: components['schemas']['HardwareConfiguration']
|
||||||
/** @description Machine Identifier (ID) for the specific Machine. */
|
/** @description Machine Identifier (ID) for the specific Machine. */
|
||||||
id: string
|
id: string
|
||||||
/** @description Information regarding the method of manufacture. */
|
/** @description Information regarding the method of manufacture. */
|
||||||
@ -126,6 +221,11 @@ export interface components {
|
|||||||
*
|
*
|
||||||
* What "close" means is up to you! */
|
* What "close" means is up to you! */
|
||||||
max_part_volume?: components['schemas']['Volume'] | null
|
max_part_volume?: components['schemas']['Volume'] | null
|
||||||
|
/**
|
||||||
|
* Format: double
|
||||||
|
* @description Progress of the current print, if printing.
|
||||||
|
*/
|
||||||
|
progress?: number | null
|
||||||
/** @description Status of the printer -- be it printing, idle, or unreachable. This may dictate if a machine is capable of taking a new job. */
|
/** @description Status of the printer -- be it printing, idle, or unreachable. This may dictate if a machine is capable of taking a new job. */
|
||||||
state: components['schemas']['MachineState']
|
state: components['schemas']['MachineState']
|
||||||
}
|
}
|
||||||
@ -140,17 +240,40 @@ export interface components {
|
|||||||
}
|
}
|
||||||
/** @description Current state of the machine -- be it printing, idle or offline. This can be used to determine if a printer is in the correct state to take a new job. */
|
/** @description Current state of the machine -- be it printing, idle or offline. This can be used to determine if a printer is in the correct state to take a new job. */
|
||||||
MachineState:
|
MachineState:
|
||||||
| 'Unknown'
|
|
||||||
| 'Idle'
|
|
||||||
| 'Running'
|
|
||||||
| 'Offline'
|
|
||||||
| 'Paused'
|
|
||||||
| 'Complete'
|
|
||||||
| {
|
| {
|
||||||
Failed: string | null
|
/** @enum {string} */
|
||||||
|
state: 'unknown'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
state: 'idle'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
state: 'running'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
state: 'offline'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
state: 'paused'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @enum {string} */
|
||||||
|
state: 'complete'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/** @description A human-readable message describing the failure. */
|
||||||
|
message?: string | null
|
||||||
|
/** @enum {string} */
|
||||||
|
state: 'failed'
|
||||||
}
|
}
|
||||||
/** @description Specific technique by which this Machine takes a design, and produces a real-world 3D object. */
|
/** @description Specific technique by which this Machine takes a design, and produces a real-world 3D object. */
|
||||||
MachineType: 'Stereolithography' | 'FusedDeposition' | 'Cnc'
|
MachineType: 'stereolithography' | 'fused_deposition' | 'cnc'
|
||||||
|
/** @description A nozzle diameter. */
|
||||||
|
NozzleDiameter: '0.2' | '0.4' | '0.6' | '0.8'
|
||||||
/** @description The response from the `/ping` endpoint. */
|
/** @description The response from the `/ping` endpoint. */
|
||||||
Pong: {
|
Pong: {
|
||||||
/** @description The pong response. */
|
/** @description The pong response. */
|
||||||
@ -169,7 +292,56 @@ export interface components {
|
|||||||
job_name: string
|
job_name: string
|
||||||
/** @description The machine id to print to. */
|
/** @description The machine id to print to. */
|
||||||
machine_id: string
|
machine_id: string
|
||||||
|
/** @description Requested design-specific slicer configurations. */
|
||||||
|
slicer_configuration?: components['schemas']['SlicerConfiguration'] | null
|
||||||
}
|
}
|
||||||
|
/** @description The slicer configuration is a set of parameters that are passed to the slicer to control how the gcode is generated. */
|
||||||
|
SlicerConfiguration: {
|
||||||
|
/**
|
||||||
|
* Format: uint
|
||||||
|
* @description The filament to use for the print.
|
||||||
|
*/
|
||||||
|
filament_idx?: number | null
|
||||||
|
}
|
||||||
|
/** @description The print stage. These come from: https://github.com/SoftFever/OrcaSlicer/blob/431978baf17961df90f0d01871b0ad1d839d7f5d/src/slic3r/GUI/DeviceManager.cpp#L78 */
|
||||||
|
Stage:
|
||||||
|
| 'nothing'
|
||||||
|
| 'empty'
|
||||||
|
| 'auto_bed_leveling'
|
||||||
|
| 'heatbed_preheating'
|
||||||
|
| 'sweeping_xy_mech_mode'
|
||||||
|
| 'changing_filament'
|
||||||
|
| 'm400_pause'
|
||||||
|
| 'paused_due_to_filament_runout'
|
||||||
|
| 'heating_hotend'
|
||||||
|
| 'calibrating_extrusion'
|
||||||
|
| 'scanning_bed_surface'
|
||||||
|
| 'inspecting_first_layer'
|
||||||
|
| 'identifying_build_plate_type'
|
||||||
|
| 'calibrating_micro_lidar'
|
||||||
|
| 'homing_toolhead'
|
||||||
|
| 'cleaning_nozzle_tip'
|
||||||
|
| 'checking_extruder_temperature'
|
||||||
|
| 'printing_was_paused_by_the_user'
|
||||||
|
| 'pause_of_front_cover_falling'
|
||||||
|
| 'calibrating_micro_lidar2'
|
||||||
|
| 'calibrating_extrusion_flow'
|
||||||
|
| 'paused_due_to_nozzle_temperature_malfunction'
|
||||||
|
| 'paused_due_to_heat_bed_temperature_malfunction'
|
||||||
|
| 'filament_unloading'
|
||||||
|
| 'skip_step_pause'
|
||||||
|
| 'filament_loading'
|
||||||
|
| 'motor_noise_calibration'
|
||||||
|
| 'paused_due_to_ams_lost'
|
||||||
|
| 'paused_due_to_low_speed_of_the_heat_break_fan'
|
||||||
|
| 'paused_due_to_chamber_temperature_control_error'
|
||||||
|
| 'cooling_chamber'
|
||||||
|
| 'paused_by_the_gcode_inserted_by_the_user'
|
||||||
|
| 'motor_noise_showoff'
|
||||||
|
| 'nozzle_filament_covered_detected_pause'
|
||||||
|
| 'cutter_error_pause'
|
||||||
|
| 'first_layer_error_pause'
|
||||||
|
| 'nozzle_clog_pause'
|
||||||
/** @description Set of three values to represent the extent of a 3-D Volume. This contains the width, depth, and height values, generally used to represent some maximum or minimum.
|
/** @description Set of three values to represent the extent of a 3-D Volume. This contains the width, depth, and height values, generally used to represent some maximum or minimum.
|
||||||
*
|
*
|
||||||
* All measurements are in millimeters. */
|
* All measurements are in millimeters. */
|
||||||
@ -278,6 +450,28 @@ export interface operations {
|
|||||||
'5XX': components['responses']['Error']
|
'5XX': components['responses']['Error']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
get_metrics: {
|
||||||
|
parameters: {
|
||||||
|
query?: never
|
||||||
|
header?: never
|
||||||
|
path?: never
|
||||||
|
cookie?: never
|
||||||
|
}
|
||||||
|
requestBody?: never
|
||||||
|
responses: {
|
||||||
|
/** @description successful operation */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown
|
||||||
|
}
|
||||||
|
content: {
|
||||||
|
'application/json': string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'4XX': components['responses']['Error']
|
||||||
|
'5XX': components['responses']['Error']
|
||||||
|
}
|
||||||
|
}
|
||||||
ping: {
|
ping: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never
|
query?: never
|
||||||
|
@ -85,7 +85,11 @@ export class MachineManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._machines = await window.electron.listMachines()
|
if (this._machineApiIp === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this._machines = await window.electron.listMachines(this._machineApiIp)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateMachineApiIp(): Promise<void> {
|
private async updateMachineApiIp(): Promise<void> {
|
||||||
|
@ -14,6 +14,7 @@ import { codeManager } from 'lib/singletons'
|
|||||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
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'
|
||||||
|
|
||||||
// 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
|
||||||
@ -108,7 +109,9 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
code = await window.electron.readFile(currentFilePath)
|
code = await window.electron.readFile(currentFilePath, {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
})
|
||||||
code = normalizeLineEndings(code)
|
code = normalizeLineEndings(code)
|
||||||
|
|
||||||
// Update both the state and the editor's code.
|
// Update both the state and the editor's code.
|
||||||
@ -182,7 +185,3 @@ export const homeLoader: LoaderFunction = async (): Promise<
|
|||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeLineEndings = (str: string, normalized = '\n') => {
|
|
||||||
return str.replace(/\r?\n/g, normalized)
|
|
||||||
}
|
|
||||||
|
@ -37,8 +37,6 @@ if (!process.env.NODE_ENV)
|
|||||||
// dotenv override when present
|
// dotenv override when present
|
||||||
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
|
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
|
||||||
|
|
||||||
console.log(process.env)
|
|
||||||
|
|
||||||
process.env.VITE_KC_API_WS_MODELING_URL ??=
|
process.env.VITE_KC_API_WS_MODELING_URL ??=
|
||||||
'wss://api.zoo.dev/ws/modeling/commands'
|
'wss://api.zoo.dev/ws/modeling/commands'
|
||||||
process.env.VITE_KC_API_BASE_URL ??= 'https://api.zoo.dev'
|
process.env.VITE_KC_API_BASE_URL ??= 'https://api.zoo.dev'
|
||||||
@ -238,6 +236,7 @@ ipcMain.handle('find_machine_api', () => {
|
|||||||
const ip = service.addresses[0]
|
const ip = service.addresses[0]
|
||||||
const port = service.port
|
const port = service.port
|
||||||
// We want to return the ip address of the machine API.
|
// We want to return the ip address of the machine API.
|
||||||
|
console.log(`Machine API found at ${ip}:${port}`)
|
||||||
resolve(`${ip}:${port}`)
|
resolve(`${ip}:${port}`)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -287,7 +286,10 @@ app.on('ready', () => {
|
|||||||
|
|
||||||
autoUpdater.on('update-downloaded', (info) => {
|
autoUpdater.on('update-downloaded', (info) => {
|
||||||
console.log('update-downloaded', info)
|
console.log('update-downloaded', info)
|
||||||
mainWindow?.webContents.send('update-downloaded', info.version)
|
mainWindow?.webContents.send('update-downloaded', {
|
||||||
|
version: info.version,
|
||||||
|
releaseNotes: info.releaseNotes,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('app.restart', () => {
|
ipcMain.handle('app.restart', () => {
|
||||||
|
@ -16,11 +16,12 @@ const startDeviceFlow = (host: string): Promise<string> =>
|
|||||||
ipcRenderer.invoke('startDeviceFlow', host)
|
ipcRenderer.invoke('startDeviceFlow', host)
|
||||||
const loginWithDeviceFlow = (): Promise<string> =>
|
const loginWithDeviceFlow = (): Promise<string> =>
|
||||||
ipcRenderer.invoke('loginWithDeviceFlow')
|
ipcRenderer.invoke('loginWithDeviceFlow')
|
||||||
|
const onUpdateDownloaded = (
|
||||||
|
callback: (value: { version: string; releaseNotes: string }) => void
|
||||||
|
) => ipcRenderer.on('update-downloaded', (_event, value) => callback(value))
|
||||||
const onUpdateDownloadStart = (
|
const onUpdateDownloadStart = (
|
||||||
callback: (value: { version: string }) => void
|
callback: (value: { version: string }) => void
|
||||||
) => ipcRenderer.on('update-download-start', (_event, value) => callback(value))
|
) => ipcRenderer.on('update-download-start', (_event, value) => callback(value))
|
||||||
const onUpdateDownloaded = (callback: (value: string) => void) =>
|
|
||||||
ipcRenderer.on('update-downloaded', (_event, value) => callback(value))
|
|
||||||
const onUpdateError = (callback: (value: Error) => void) =>
|
const onUpdateError = (callback: (value: Error) => void) =>
|
||||||
ipcRenderer.on('update-error', (_event, value) => callback(value))
|
ipcRenderer.on('update-error', (_event, value) => callback(value))
|
||||||
const appRestart = () => ipcRenderer.invoke('app.restart')
|
const appRestart = () => ipcRenderer.invoke('app.restart')
|
||||||
@ -29,22 +30,51 @@ const isMac = os.platform() === 'darwin'
|
|||||||
const isWindows = os.platform() === 'win32'
|
const isWindows = os.platform() === 'win32'
|
||||||
const isLinux = os.platform() === 'linux'
|
const isLinux = os.platform() === 'linux'
|
||||||
|
|
||||||
let fsWatchListeners = new Map<string, ReturnType<typeof chokidar.watch>>()
|
let fsWatchListeners = new Map<
|
||||||
|
string,
|
||||||
|
Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
watcher: ReturnType<typeof chokidar.watch>
|
||||||
|
callback: (eventType: string, path: string) => void
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>()
|
||||||
|
|
||||||
const watchFileOn = (path: string, callback: (path: string) => void) => {
|
const watchFileOn = (
|
||||||
const watcherMaybe = fsWatchListeners.get(path)
|
path: string,
|
||||||
if (watcherMaybe) return
|
key: string,
|
||||||
const watcher = chokidar.watch(path)
|
callback: (eventType: string, path: string) => void
|
||||||
|
) => {
|
||||||
|
let watchers = fsWatchListeners.get(path)
|
||||||
|
if (!watchers) {
|
||||||
|
watchers = new Map()
|
||||||
|
}
|
||||||
|
const watcher = chokidar.watch(path, { depth: 1 })
|
||||||
watcher.on('all', callback)
|
watcher.on('all', callback)
|
||||||
fsWatchListeners.set(path, watcher)
|
watchers.set(key, { watcher, callback })
|
||||||
|
fsWatchListeners.set(path, watchers)
|
||||||
}
|
}
|
||||||
const watchFileOff = (path: string) => {
|
const watchFileOff = (path: string, key: string) => {
|
||||||
const watcher = fsWatchListeners.get(path)
|
const watchers = fsWatchListeners.get(path)
|
||||||
if (!watcher) return
|
if (!watchers) return
|
||||||
watcher.unwatch(path)
|
const data = watchers.get(key)
|
||||||
fsWatchListeners.delete(path)
|
if (!data) {
|
||||||
|
console.warn(
|
||||||
|
"Trying to remove a watcher, callback that doesn't exist anymore. Suspicious."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { watcher, callback } = data
|
||||||
|
watcher.off('all', callback)
|
||||||
|
watchers.delete(key)
|
||||||
|
if (watchers.size === 0) {
|
||||||
|
fsWatchListeners.delete(path)
|
||||||
|
} else {
|
||||||
|
fsWatchListeners.set(path, watchers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const readFile = (path: string) => fs.readFile(path, 'utf-8')
|
const readFile = fs.readFile
|
||||||
// It seems like from the node source code this does not actually block but also
|
// It seems like from the node source code this does not actually block but also
|
||||||
// don't trust me on that (jess).
|
// don't trust me on that (jess).
|
||||||
const exists = (path: string) => fsSync.existsSync(path)
|
const exists = (path: string) => fsSync.existsSync(path)
|
||||||
@ -76,11 +106,12 @@ const kittycad = (access: string, args: any) =>
|
|||||||
|
|
||||||
// We could probably do this from the renderer side, but I fear CORS will
|
// We could probably do this from the renderer side, but I fear CORS will
|
||||||
// bite our butts.
|
// bite our butts.
|
||||||
const listMachines = async (): Promise<MachinesListing> => {
|
const listMachines = async (
|
||||||
const machineApi = await ipcRenderer.invoke('find_machine_api')
|
machineApiAddr: string
|
||||||
if (!machineApi) return []
|
): Promise<MachinesListing> => {
|
||||||
|
return fetch(`http://${machineApiAddr}/machines`).then((resp) => {
|
||||||
return fetch(`http://${machineApi}/machines`).then((resp) => resp.json())
|
return resp.json()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMachineApiIp = async (): Promise<String | null> =>
|
const getMachineApiIp = async (): Promise<String | null> =>
|
||||||
|
64
src/wasm-lib/Cargo.lock
generated
@ -1394,9 +1394,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.2"
|
version = "0.25.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
|
checksum = "d97eb9a8e0cd5b76afea91d7eecd5cf8338cd44ced04256cf1f800474b227c52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
@ -1533,16 +1533,16 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.71"
|
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 = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b"
|
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.20"
|
version = "0.2.22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
@ -1617,7 +1617,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
version = "0.1.12"
|
version = "0.1.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hyper 0.14.30",
|
"hyper 0.14.30",
|
||||||
@ -2337,18 +2337,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.86"
|
version = "1.0.88"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3"
|
name = "pyo3"
|
||||||
version = "0.22.3"
|
version = "0.22.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225"
|
checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"indoc",
|
"indoc",
|
||||||
@ -2364,9 +2364,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3-build-config"
|
name = "pyo3-build-config"
|
||||||
version = "0.22.3"
|
version = "0.22.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3"
|
checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
@ -2374,9 +2374,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3-ffi"
|
name = "pyo3-ffi"
|
||||||
version = "0.22.3"
|
version = "0.22.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c"
|
checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"pyo3-build-config",
|
"pyo3-build-config",
|
||||||
@ -2384,9 +2384,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3-macros"
|
name = "pyo3-macros"
|
||||||
version = "0.22.3"
|
version = "0.22.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28"
|
checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"pyo3-macros-backend",
|
"pyo3-macros-backend",
|
||||||
@ -2396,9 +2396,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3-macros-backend"
|
name = "pyo3-macros-backend"
|
||||||
version = "0.22.3"
|
version = "0.22.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1"
|
checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -3829,9 +3829,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.10.0"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3907,9 +3907,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.94"
|
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 = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887"
|
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -3918,9 +3918,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.94"
|
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 = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e"
|
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
@ -3946,9 +3946,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.94"
|
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 = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7"
|
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@ -3956,9 +3956,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.94"
|
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 = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6"
|
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -3969,9 +3969,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.94"
|
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 = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9"
|
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-lib"
|
name = "wasm-lib"
|
||||||
@ -4032,9 +4032,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.70"
|
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 = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
|
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
@ -18,31 +18,31 @@ kittycad.workspace = true
|
|||||||
serde_json = "1.0.128"
|
serde_json = "1.0.128"
|
||||||
tokio = { version = "1.40.0", features = ["sync"] }
|
tokio = { version = "1.40.0", features = ["sync"] }
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||||
wasm-bindgen = "0.2.91"
|
wasm-bindgen = "0.2.91"
|
||||||
wasm-bindgen-futures = "0.4.44"
|
wasm-bindgen-futures = "0.4.44"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
image = { version = "0.25.3", default-features = false, features = ["png"] }
|
||||||
kittycad = { workspace = true, default-features = true }
|
kittycad = { workspace = true, default-features = true }
|
||||||
kittycad-modeling-cmds = { workspace = true }
|
kittycad-modeling-cmds = { workspace = true }
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
reqwest = { version = "0.12", default-features = false }
|
reqwest = { version = "0.12", default-features = false }
|
||||||
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
|
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||||
twenty-twenty = "0.8"
|
twenty-twenty = "0.8"
|
||||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
js-sys = "0.3.71"
|
js-sys = "0.3.72"
|
||||||
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
||||||
wasm-bindgen-futures = { version = "0.4.44", features = ["futures-core-03-stream"] }
|
wasm-bindgen-futures = { version = "0.4.44", features = ["futures-core-03-stream"] }
|
||||||
wasm-streams = "0.4.1"
|
wasm-streams = "0.4.1"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
||||||
version = "0.3.69"
|
version = "0.3.72"
|
||||||
features = [
|
features = [
|
||||||
"console",
|
"console",
|
||||||
"HtmlTextAreaElement",
|
"HtmlTextAreaElement",
|
||||||
|
@ -762,7 +762,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
|||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -17,7 +17,7 @@ mod test_examples_someFn {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -17,7 +17,7 @@ mod test_examples_someFn {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -17,7 +17,7 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -51,7 +51,7 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -17,7 +17,7 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -18,7 +18,7 @@ mod test_examples_my_func {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -52,7 +52,7 @@ mod test_examples_my_func {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -18,7 +18,7 @@ mod test_examples_line_to {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -52,7 +52,7 @@ mod test_examples_line_to {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -17,7 +17,7 @@ mod test_examples_min {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -51,7 +51,7 @@ mod test_examples_min {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -17,7 +17,7 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -17,7 +17,7 @@ mod test_examples_import {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -17,7 +17,7 @@ mod test_examples_import {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -17,7 +17,7 @@ mod test_examples_import {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator).await.unwrap();
|
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|