Compare commits

..

13 Commits

Author SHA1 Message Date
5bea90ad9a add API fallback for engine utils lib 2024-10-22 12:58:35 -07:00
d23ddc19eb Merge branch 'main' into mike/engine-utils-wasm 2024-10-22 10:27:12 -07:00
e65358f635 add needed tangential arc params to the serializable path 2024-10-11 11:11:27 -07:00
0a1201e680 cleanup 2024-10-11 10:55:55 -07:00
9db013e672 fmt 2024-10-11 10:47:17 -07:00
0196d72a2d mini code cleanup 2024-10-11 10:44:46 -07:00
e6af4078bd actual working arcs using engine utils 2024-10-10 18:19:26 -07:00
2b233dc705 it works! 2024-10-10 16:58:12 -07:00
b11e8af9c7 _ 2024-10-10 11:52:33 -07:00
c017847d7b Fix to convert JS Promise to async Rust 2024-10-09 00:09:19 -04:00
9635eea8c1 walking away 2024-10-08 13:15:24 -07:00
5a2df642b1 thrashing 2024-10-08 12:00:34 -07:00
621e41080e delme 2024-10-07 11:36:33 -07:00
309 changed files with 24286 additions and 63832 deletions

BIN
..env.development.local.swp Normal file

Binary file not shown.

View File

@ -1,3 +1,4 @@
src/wasm-lib/* src/wasm-lib/*
src/lib/engine-utils/engine.js
*.typegen.ts *.typegen.ts
packages/codemirror-lsp-client/dist/* packages/codemirror-lsp-client/dist/*

View File

@ -15,7 +15,6 @@ on:
env: env:
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }} CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }} BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -26,6 +25,7 @@ jobs:
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows) runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
outputs: outputs:
version: ${{ steps.export_version.outputs.version }} version: ${{ steps.export_version.outputs.version }}
notes: ${{ steps.export_version.outputs.notes }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -55,6 +55,8 @@ jobs:
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json # TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
- name: Generate release notes - name: Generate release notes
env:
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
run: | run: |
echo "$NOTES" > release-notes.md echo "$NOTES" > release-notes.md
cat release-notes.md cat release-notes.md
@ -82,6 +84,9 @@ jobs:
path: | path: |
electron-builder.yml electron-builder.yml
- id: export_notes
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
- name: Prepare electron-builder.yml file for updater test - name: Prepare electron-builder.yml file for updater test
if: ${{ env.CUT_RELEASE_PR == 'true' }} if: ${{ env.CUT_RELEASE_PR == 'true' }}
run: | run: |
@ -109,8 +114,17 @@ jobs:
platform: linux platform: linux
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env: env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }} VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -172,25 +186,8 @@ jobs:
smksp_cert_sync.exe smksp_cert_sync.exe
shell: cmd shell: cmd
- name: Build the app (debug) - name: Build the app
if: ${{ env.BUILD_RELEASE == 'false' }} run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
# electron-builder doesn't have a concept of release vs debug,
# this is just not doing any codesign or release yml generation
run: yarn electron-builder --config
- name: Build the app (release)
if: ${{ env.BUILD_RELEASE == 'true' }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: yarn electron-builder --config --publish always
- name: List artifacts in out/ - name: List artifacts in out/
run: ls -R out run: ls -R out
@ -234,17 +231,7 @@ jobs:
- name: Build the app (updater-test) - name: Build the app (updater-test)
if: ${{ env.CUT_RELEASE_PR == 'true' }} if: ${{ env.CUT_RELEASE_PR == 'true' }}
env: run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: yarn electron-builder --config --publish always
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
if: ${{ env.CUT_RELEASE_PR == 'true' }} if: ${{ env.CUT_RELEASE_PR == 'true' }}
@ -275,6 +262,7 @@ jobs:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }} VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }} PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
NOTES: ${{ needs.prepare-files.outputs.notes }}
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }} BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }} WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }} URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}

View File

@ -37,4 +37,4 @@ jobs:
# We specifically want to test the disable-println feature # We specifically want to test the disable-println feature
# Since it is not enabled by default, we need to specify it # Since it is not enabled by default, we need to specify it
# This is used in kcl-lsp # This is used in kcl-lsp
cargo check --workspace --features disable-println --features pyo3 --features cli cargo check --all --features disable-println --features pyo3 --features cli

View File

@ -62,7 +62,7 @@ jobs:
shell: bash shell: bash
run: |- run: |-
cd "${{ matrix.dir }}" cd "${{ matrix.dir }}"
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log cargo llvm-cov nextest --all --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
env: env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000 RUST_MIN_STACK: 10485760000

4
.gitignore vendored
View File

@ -66,3 +66,7 @@ venv
# electron # electron
out/ out/
# engine wasm utils
src/lib/engine-utils/engine.wasm
src/lib/engine-utils/engine.js

2
.nvmrc
View File

@ -1 +1 @@
v21.7.3 v21.7.1

View File

@ -74,12 +74,10 @@ layout: manual
* [`patternTransform`](kcl/patternTransform) * [`patternTransform`](kcl/patternTransform)
* [`pi`](kcl/pi) * [`pi`](kcl/pi)
* [`polar`](kcl/polar) * [`polar`](kcl/polar)
* [`polygon`](kcl/polygon)
* [`pow`](kcl/pow) * [`pow`](kcl/pow)
* [`profileStart`](kcl/profileStart) * [`profileStart`](kcl/profileStart)
* [`profileStartX`](kcl/profileStartX) * [`profileStartX`](kcl/profileStartX)
* [`profileStartY`](kcl/profileStartY) * [`profileStartY`](kcl/profileStartY)
* [`push`](kcl/push)
* [`reduce`](kcl/reduce) * [`reduce`](kcl/reduce)
* [`rem`](kcl/rem) * [`rem`](kcl/rem)
* [`revolve`](kcl/revolve) * [`revolve`](kcl/revolve)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -23,11 +23,11 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Literal`| | No | | `type` |enum: `Literal`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No | | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
| `raw` |`string`| | No | | `raw` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -43,10 +43,10 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No | | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `name` |`string`| | No | | `name` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -62,12 +62,12 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `BinaryExpression`| | No | | `type` |enum: `BinaryExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| | No | | `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| | No |
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No | | `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No | | `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -83,12 +83,12 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `CallExpression`| | No | | `type` |enum: `CallExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| | No | | `callee` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No | | `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `optional` |`boolean`| | No | | `optional` |`boolean`| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -104,11 +104,11 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No | | `type` |enum: `UnaryExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| | No | | `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No | | `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -124,12 +124,12 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No | | `type` |enum: `MemberExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No | | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No | | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
| `computed` |`boolean`| | No | | `computed` |`boolean`| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -145,13 +145,13 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No | | `type` |enum: `IfExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No | | `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No | | `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No | | `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| | No | | `final_else` |[`Program`](/docs/kcl/types/Program)| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----

View File

@ -23,12 +23,12 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `ImportStatement`| | No | | `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 | | `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
| `path` |`string`| | No | | `path` |`string`| | No |
| `raw_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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -44,10 +44,10 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `ExpressionStatement`| | No | | `type` |enum: `ExpressionStatement`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `expression` |[`Expr`](/docs/kcl/types/Expr)| | No | | `expression` |[`Expr`](/docs/kcl/types/Expr)| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -63,12 +63,12 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `VariableDeclaration`| | No | | `type` |enum: `VariableDeclaration`| | No |
| `start` |[`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -84,10 +84,10 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `ReturnStatement`| | No | | `type` |enum: `ReturnStatement`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `argument` |[`Expr`](/docs/kcl/types/Expr)| | No | | `argument` |[`Expr`](/docs/kcl/types/Expr)| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----

View File

@ -15,10 +15,10 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No | | `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No | | `then_val` |[`Program`](/docs/kcl/types/Program)| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -16,6 +16,6 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `bindings` |`object`| | No | | `bindings` |`object`| | No |
| `parent` |`integer`| | No | | `parent` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |

View File

@ -24,11 +24,11 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Literal`| | No | | `type` |enum: `Literal`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No | | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No |
| `raw` |`string`| | No | | `raw` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -44,10 +44,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No | | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `name` |`string`| | No | | `name` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -63,10 +63,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No | | `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `value` |`string`| | No | | `value` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -82,12 +82,12 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `BinaryExpression`| | No | | `type` |enum: `BinaryExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No | | `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | | `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | | `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -103,11 +103,11 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)| | No | | `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No | | `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
| `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | | `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -123,12 +123,12 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `CallExpression`| | No | | `type` |enum: `CallExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No | | `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No |
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No | | `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `optional` |`boolean`| | No | | `optional` |`boolean`| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -144,11 +144,11 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `PipeExpression`| | No | | `type` |enum: `PipeExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No | | `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -164,9 +164,9 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `PipeSubstitution`| | No | | `type` |enum: `PipeSubstitution`| | 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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -182,11 +182,11 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `ArrayExpression`| | No | | `type` |enum: `ArrayExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No | | `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -202,12 +202,12 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `ArrayRangeExpression`| | No | | `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 | | `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 | | `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 | | `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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -223,11 +223,11 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `ObjectExpression`| | No | | `type` |enum: `ObjectExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`| | No | | `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -243,12 +243,12 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No | | `type` |enum: `MemberExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No | | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No | | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No |
| `computed` |`boolean`| | No | | `computed` |`boolean`| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -264,11 +264,11 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No | | `type` |enum: `UnaryExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No | | `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | | `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -284,13 +284,13 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No | | `type` |enum: `IfExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | | `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | | `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No | | `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | | `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -307,8 +307,8 @@ KCL value for an optional parameter which was not given an argument. (remember,
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `None`| | No | | `type` |enum: `None`| | No |
| `start` |`integer`| | No | | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |`integer`| | No | | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
---- ----

View File

@ -15,10 +15,10 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No | | `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
| `body` |[`Program`](/docs/kcl/types/Program)| | No | | `body` |[`Program`](/docs/kcl/types/Program)| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -15,9 +15,9 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `name` |`string`| | No | | `name` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -17,8 +17,8 @@ layout: manual
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No | | `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 | | `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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -59,10 +59,10 @@ Any KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No | | `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `value` |`string`| | No | | `value` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----

View File

@ -23,10 +23,10 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No | | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `name` |`string`| | No | | `name` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -42,11 +42,11 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Literal`| | No | | `type` |enum: `Literal`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No | | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
| `raw` |`string`| | No | | `raw` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----

View File

@ -23,12 +23,12 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No | | `type` |enum: `MemberExpression`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No | | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No | | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
| `computed` |`boolean`| | No | | `computed` |`boolean`| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----
@ -44,10 +44,10 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No | | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `name` |`string`| | No | | `name` |`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 | | `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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
---- ----

View File

@ -16,7 +16,7 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `nonCodeNodes` |`object`| | No | | `nonCodeNodes` |`object`| | No |
| `startNodes` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | No | | `start` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | 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 |

View File

@ -15,9 +15,9 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)| | No | | `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -15,10 +15,10 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `key` |[`Identifier`](/docs/kcl/types/Identifier)| | No | | `key` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `value` |[`Expr`](/docs/kcl/types/Expr)| | No | | `value` |[`Expr`](/docs/kcl/types/Expr)| | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -162,28 +162,6 @@ A base path.
---- ----
A circular arc, not necessarily tangential to the current point.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Arc`| | No |
| `center` |`[number, number]`| Center of the circle that this arc is drawn on. | No |
| `radius` |`number`| Radius of the circle that this arc is drawn on. | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
----

View File

@ -1,24 +0,0 @@
---
title: "PolygonData"
excerpt: "Data for drawing a polygon"
layout: manual
---
Data for drawing a polygon
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `radius` |`number`| The radius of the polygon | No |
| `numSides` |`integer`| The number of sides in the polygon | No |
| `center` |`[number, number]`| The center point of the polygon | No |
| `inscribed` |`boolean`| Whether the polygon is inscribed (true) or circumscribed (false) about a circle with the specified radius | No |

View File

@ -16,10 +16,10 @@ A KCL program top level, or function body.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No | | `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No | | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -16,7 +16,7 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No | | `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
| `currentEnv` |`integer`| | No | | `currentEnv` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No | | `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |

View File

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

View File

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

View File

@ -18,7 +18,7 @@ Engine information for a tag.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `id` |`string`| The id of the tagged object. | No | | `id` |`string`| The id of the tagged object. | No |
| `sketch` |`string`| The sketch the tag is on. | No | | `sketch` |`string`| The sketch the tag is on. | No |
| `path` |[`Path`](/docs/kcl/types/Path)| The path the tag is on. | No | | `path` |[`BasePath`](/docs/kcl/types/BasePath)| The path the tag is on. | No |
| `surface` |[`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface)| The surface information for the tag. | No | | `surface` |[`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface)| The surface information for the tag. | No |

View File

@ -15,10 +15,10 @@ layout: manual
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No | | `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No |
| `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No | | `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | 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 |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -86,7 +86,7 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %) |> line([0, ${commonPoints.num1 + 0.01}], %)
|> lineTo([0, ${commonPoints.num3}], %)`) |> line([-${commonPoints.num2}, 0], %)`)
} }
// deselect line tool // deselect line tool

View File

@ -3,7 +3,6 @@ import { test, expect } from './fixtures/fixtureSetup'
import * as fsp from 'fs/promises' import * as fsp from 'fs/promises'
import * as fs from 'fs' import * as fs from 'fs'
import { import {
createProject,
executorInputPath, executorInputPath,
getUtils, getUtils,
setup, setup,
@ -115,15 +114,20 @@ test.describe('when using the file tree to', () => {
async ({ browser: _, tronApp }, testInfo) => { async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise() await tronApp.initialise()
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } = const {
await getUtils(tronApp.page, test) panesOpen,
createAndSelectProject,
pasteCodeInEditor,
renameFile,
editorTextMatches,
} = await getUtils(tronApp.page, test)
await tronApp.page.setViewportSize({ width: 1200, height: 500 }) await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log) tronApp.page.on('console', console.log)
await panesOpen(['files', 'code']) await panesOpen(['files', 'code'])
await createProject({ name: 'project-000', page: tronApp.page }) await createAndSelectProject('project-000')
// File the main.kcl with contents // File the main.kcl with contents
const kclCube = await fsp.readFile( const kclCube = await fsp.readFile(
@ -163,14 +167,15 @@ test.describe('when using the file tree to', () => {
async ({ browser: _, tronApp }, testInfo) => { async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise() await tronApp.initialise()
const { panesOpen, createNewFile } = await getUtils(tronApp.page, test) const { panesOpen, createAndSelectProject, createNewFile } =
await getUtils(tronApp.page, test)
await tronApp.page.setViewportSize({ width: 1200, height: 500 }) await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log) tronApp.page.on('console', console.log)
await panesOpen(['files']) await panesOpen(['files'])
await createProject({ name: 'project-000', page: tronApp.page }) await createAndSelectProject('project-000')
await createNewFile('') await createNewFile('')
await createNewFile('') await createNewFile('')
@ -193,74 +198,62 @@ test.describe('when using the file tree to', () => {
test( test(
'create a new file with the same name as an existing file cancels the operation', 'create a new file with the same name as an existing file cancels the operation',
{ tag: '@electron' }, { tag: '@electron' },
async ( async ({ browser: _, tronApp }, testInfo) => {
{ browser: _, tronApp, homePage, scene, editor, toolbar }, await tronApp.initialise()
testInfo
) => {
const projectName = 'cube'
const mainFile = 'main.kcl'
const secondFile = 'cylinder.kcl'
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8')
const kclCylinder = await fsp.readFile(
executorInputPath('cylinder.kcl'),
'utf-8'
)
await tronApp.initialise({
fixtures: { homePage, scene, editor, toolbar },
folderSetupFn: async (dir) => {
const cubeDir = join(dir, projectName)
await fsp.mkdir(cubeDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cube.kcl'),
join(cubeDir, mainFile)
)
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(cubeDir, secondFile)
)
},
})
const { const {
openKclCodePanel,
openFilePanel, openFilePanel,
createAndSelectProject,
pasteCodeInEditor,
createNewFileAndSelect,
renameFile, renameFile,
selectFile, selectFile,
editorTextMatches, editorTextMatches,
waitForPageLoad,
} = await getUtils(tronApp.page, _test) } = await getUtils(tronApp.page, _test)
await test.step(`Setup: Open project and navigate to ${secondFile}`, async () => { await tronApp.page.setViewportSize({ width: 1200, height: 500 })
await homePage.expectState({ tronApp.page.on('console', console.log)
projectCards: [
{
title: projectName,
fileCount: 2,
folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project
},
],
sortBy: 'last-modified-desc',
})
await homePage.openProject(projectName)
await waitForPageLoad()
await openFilePanel()
await selectFile(secondFile)
})
await test.step(`Attempt to rename ${secondFile} to ${mainFile}`, async () => { await createAndSelectProject('project-000')
await renameFile(secondFile, mainFile) await openKclCodePanel()
}) await openFilePanel()
// File the main.kcl with contents
const kclCube = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cube.kcl',
'utf-8'
)
await pasteCodeInEditor(kclCube)
await test.step(`Postcondition: ${mainFile} still has the original content`, async () => { // TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
await selectFile(mainFile) await tronApp.page.waitForTimeout(2000)
const kcl1 = 'main.kcl'
const kcl2 = '2.kcl'
await createNewFileAndSelect(kcl2)
const kclCylinder = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
'utf-8'
)
await pasteCodeInEditor(kclCylinder)
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
await tronApp.page.waitForTimeout(2000)
await renameFile(kcl2, kcl1)
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {
await selectFile(kcl1)
await editorTextMatches(kclCube) await editorTextMatches(kclCube)
}) })
await tronApp.page.waitForTimeout(500)
await test.step(`Postcondition: ${secondFile} still exists with the original content`, async () => { await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
await selectFile(secondFile) await selectFile(kcl2)
await editorTextMatches(kclCylinder) await editorTextMatches(kclCylinder)
}) })
await tronApp.close() await tronApp?.close?.()
} }
) )
@ -270,15 +263,20 @@ test.describe('when using the file tree to', () => {
async ({ browser: _, tronApp }, testInfo) => { async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise() await tronApp.initialise()
const { panesOpen, pasteCodeInEditor, deleteFile, editorTextMatches } = const {
await getUtils(tronApp.page, _test) panesOpen,
createAndSelectProject,
pasteCodeInEditor,
deleteFile,
editorTextMatches,
} = await getUtils(tronApp.page, _test)
await tronApp.page.setViewportSize({ width: 1200, height: 500 }) await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log) tronApp.page.on('console', console.log)
await panesOpen(['files', 'code']) await panesOpen(['files', 'code'])
await createProject({ name: 'project-000', page: tronApp.page }) await createAndSelectProject('project-000')
// File the main.kcl with contents // File the main.kcl with contents
const kclCube = await fsp.readFile( const kclCube = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cube.kcl', 'src/wasm-lib/tests/executor/inputs/cube.kcl',
@ -286,11 +284,11 @@ test.describe('when using the file tree to', () => {
) )
await pasteCodeInEditor(kclCube) await pasteCodeInEditor(kclCube)
const mainFile = 'main.kcl' const kcl1 = 'main.kcl'
await deleteFile(mainFile) await deleteFile(kcl1)
await test.step(`Postcondition: ${mainFile} is recreated but has no content`, async () => { await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => {
await editorTextMatches('') await editorTextMatches('')
}) })
@ -308,6 +306,7 @@ test.describe('when using the file tree to', () => {
const { const {
panesOpen, panesOpen,
createAndSelectProject,
pasteCodeInEditor, pasteCodeInEditor,
createNewFile, createNewFile,
openDebugPanel, openDebugPanel,
@ -319,7 +318,7 @@ test.describe('when using the file tree to', () => {
tronApp.page.on('console', console.log) tronApp.page.on('console', console.log)
await panesOpen(['files', 'code']) await panesOpen(['files', 'code'])
await createProject({ name: 'project-000', page: tronApp.page }) await createAndSelectProject('project-000')
// Create a small file // Create a small file
const kclCube = await fsp.readFile( const kclCube = await fsp.readFile(
@ -723,7 +722,7 @@ _test.describe('Renaming in the file tree', () => {
}) })
await _test.step('Rename the folder', async () => { await _test.step('Rename the folder', async () => {
await page.waitForTimeout(1000) await page.waitForTimeout(60000)
await folderToRename.click({ button: 'right' }) await folderToRename.click({ button: 'right' })
await _expect(renameMenuItem).toBeVisible() await _expect(renameMenuItem).toBeVisible()
await renameMenuItem.click() await renameMenuItem.click()

View File

@ -1,11 +1,6 @@
import type { Page, Locator } from '@playwright/test' import type { Page, Locator } from '@playwright/test'
import { expect } from '@playwright/test' import { expect } from '@playwright/test'
import { import { sansWhitespace } from '../test-utils'
closePane,
checkIfPaneIsOpen,
openPane,
sansWhitespace,
} from '../test-utils'
interface EditorState { interface EditorState {
activeLines: Array<string> activeLines: Array<string>
@ -16,7 +11,6 @@ interface EditorState {
export class EditorFixture { export class EditorFixture {
public page: Page public page: Page
private paneButtonTestId = 'code-pane-button'
private diagnosticsTooltip!: Locator private diagnosticsTooltip!: Locator
private diagnosticsGutterIcon!: Locator private diagnosticsGutterIcon!: Locator
private codeContent!: Locator private codeContent!: Locator
@ -37,32 +31,19 @@ export class EditorFixture {
private _expectEditorToContain = private _expectEditorToContain =
(not = false) => (not = false) =>
async ( (
code: string, code: string,
{ {
shouldNormalise = false, shouldNormalise = false,
timeout = 5_000, timeout = 5_000,
}: { shouldNormalise?: boolean; timeout?: number } = {} }: { shouldNormalise?: boolean; timeout?: number } = {}
) => { ) => {
const wasPaneOpen = await this.checkIfPaneIsOpen()
if (!wasPaneOpen) {
await this.openPane()
}
const resetPane = async () => {
if (!wasPaneOpen) {
await this.closePane()
}
}
if (!shouldNormalise) { if (!shouldNormalise) {
const expectStart = expect(this.codeContent) const expectStart = expect(this.codeContent)
if (not) { if (not) {
const result = await expectStart.not.toContainText(code, { timeout }) return expectStart.not.toContainText(code, { timeout })
await resetPane()
return result
} }
const result = await expectStart.toContainText(code, { timeout }) return expectStart.toContainText(code, { timeout })
await resetPane()
return result
} }
const normalisedCode = code.replaceAll(/\s+/g, '').trim() const normalisedCode = code.replaceAll(/\s+/g, '').trim()
const expectStart = expect.poll( const expectStart = expect.poll(
@ -75,13 +56,9 @@ export class EditorFixture {
} }
) )
if (not) { if (not) {
const result = await expectStart.not.toContain(normalisedCode) return expectStart.not.toContain(normalisedCode)
await resetPane()
return result
} }
const result = await expectStart.toContain(normalisedCode) return expectStart.toContain(normalisedCode)
await resetPane()
return result
} }
expectEditor = { expectEditor = {
toContain: this._expectEditorToContain(), toContain: this._expectEditorToContain(),
@ -138,13 +115,4 @@ export class EditorFixture {
code = code.replace(findCode, replaceCode) code = code.replace(findCode, replaceCode)
await this.codeContent.fill(code) await this.codeContent.fill(code)
} }
checkIfPaneIsOpen() {
return checkIfPaneIsOpen(this.page, this.paneButtonTestId)
}
closePane() {
return closePane(this.page, this.paneButtonTestId)
}
openPane() {
return openPane(this.page, this.paneButtonTestId)
}
} }

View File

@ -20,7 +20,6 @@ export class AuthenticatedApp {
public readonly page: Page public readonly page: Page
public readonly context: BrowserContext public readonly context: BrowserContext
public readonly testInfo: TestInfo public readonly testInfo: TestInfo
public readonly viewPortSize = { width: 1000, height: 500 }
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
this.page = page this.page = page
@ -37,7 +36,7 @@ export class AuthenticatedApp {
;(window as any).playwrightSkipFilePicker = true ;(window as any).playwrightSkipFilePicker = true
}, code) }, code)
await this.page.setViewportSize(this.viewPortSize) await this.page.setViewportSize({ width: 1000, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
} }

View File

@ -10,13 +10,7 @@ import {
} from '../test-utils' } from '../test-utils'
type mouseParams = { type mouseParams = {
pixelDiff?: number pixelDiff: number
}
type mouseDragToParams = mouseParams & {
fromPoint: { x: number; y: number }
}
type mouseDragFromParams = mouseParams & {
toPoint: { x: number; y: number }
} }
type SceneSerialised = { type SceneSerialised = {
@ -26,13 +20,6 @@ type SceneSerialised = {
} }
} }
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
type DragFromHandler = (
dragParams: mouseDragFromParams
) => Promise<void | boolean>
export class SceneFixture { export class SceneFixture {
public page: Page public page: Page
@ -68,7 +55,7 @@ export class SceneFixture {
x: number, x: number,
y: number, y: number,
{ steps }: { steps: number } = { steps: 20 } { steps }: { steps: number } = { steps: 20 }
): [ClickHandler, MoveHandler] => ) =>
[ [
(clickParams?: mouseParams) => { (clickParams?: mouseParams) => {
if (clickParams?.pixelDiff) { if (clickParams?.pixelDiff) {
@ -91,47 +78,6 @@ export class SceneFixture {
return this.page.mouse.move(x, y, { steps }) return this.page.mouse.move(x, y, { steps })
}, },
] as const ] as const
makeDragHelpers = (
x: number,
y: number,
{ steps }: { steps: number } = { steps: 20 }
): [DragToHandler, DragFromHandler] =>
[
(dragToParams: mouseDragToParams) => {
if (dragToParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
() =>
this.page.dragAndDrop('#stream', '#stream', {
sourcePosition: dragToParams.fromPoint,
targetPosition: { x, y },
}),
dragToParams.pixelDiff
)
}
return this.page.dragAndDrop('#stream', '#stream', {
sourcePosition: dragToParams.fromPoint,
targetPosition: { x, y },
})
},
(dragFromParams: mouseDragFromParams) => {
if (dragFromParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
() =>
this.page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x, y },
targetPosition: dragFromParams.toPoint,
}),
dragFromParams.pixelDiff
)
}
return this.page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x, y },
targetPosition: dragFromParams.toPoint,
})
},
] as const
/** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene. /** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene.
* *

View File

@ -7,7 +7,6 @@ export class ToolbarFixture {
extrudeButton!: Locator extrudeButton!: Locator
startSketchBtn!: Locator startSketchBtn!: Locator
lineBtn!: Locator
rectangleBtn!: Locator rectangleBtn!: Locator
exitSketchBtn!: Locator exitSketchBtn!: Locator
editSketchBtn!: Locator editSketchBtn!: Locator
@ -25,7 +24,6 @@ export class ToolbarFixture {
this.page = page this.page = page
this.extrudeButton = page.getByTestId('extrude') this.extrudeButton = page.getByTestId('extrude')
this.startSketchBtn = page.getByTestId('sketch') this.startSketchBtn = page.getByTestId('sketch')
this.lineBtn = page.getByTestId('line')
this.rectangleBtn = page.getByTestId('corner-rectangle') this.rectangleBtn = page.getByTestId('corner-rectangle')
this.exitSketchBtn = page.getByTestId('sketch-exit') this.exitSketchBtn = page.getByTestId('sketch-exit')
this.editSketchBtn = page.getByText('Edit Sketch') this.editSketchBtn = page.getByText('Edit Sketch')

View File

@ -7,7 +7,6 @@ import {
setupElectron, setupElectron,
tearDown, tearDown,
executorInputPath, executorInputPath,
createProject,
} from './test-utils' } from './test-utils'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
@ -56,48 +55,6 @@ test.describe('Onboarding tests', () => {
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
}) })
test(
'Desktop: fresh onboarding executes and loads',
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
appSettings: {
app: {
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
})
const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 }
await page.setViewportSize(viewportSize)
await test.step(`Create a project and open to the onboarding`, async () => {
await createProject({ name: 'project-link', page })
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
await u.waitForPageLoad()
})
})
await test.step(`Ensure we see the onboarding stuff`, async () => {
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
// *and* that the code is shown in the editor
await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
})
await electronApp.close()
}
)
test('Code resets after confirmation', async ({ page }) => { test('Code resets after confirmation', async ({ page }) => {
const initialCode = `sketch001 = startSketchOn('XZ')` const initialCode = `sketch001 = startSketchOn('XZ')`
@ -421,9 +378,7 @@ test(
const restartConfirmationButton = page.getByRole('button', { const restartConfirmationButton = page.getByRole('button', {
name: 'Make a new project', name: 'Make a new project',
}) })
const tutorialProjectIndicator = page const tutorialProjectIndicator = page.getByText('Tutorial Project 00')
.getByTestId('project-sidebar-toggle')
.filter({ hasText: 'Tutorial Project 00' })
const tutorialModalText = page.getByText('Welcome to Modeling App!') const tutorialModalText = page.getByText('Welcome to Modeling App!')
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' }) const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
const userMenuButton = page.getByTestId('user-sidebar-toggle') const userMenuButton = page.getByTestId('user-sidebar-toggle')

View File

@ -451,103 +451,3 @@ sketch002 = startSketchOn(extrude001, seg03)
} }
) )
}) })
test(`Verify axis and origin snapping`, async ({
app,
editor,
toolbar,
scene,
}) => {
// Constants and locators
// These are mappings from screenspace to KCL coordinates,
// until we merge in our coordinate system helpers
const xzPlane = [
app.viewPortSize.width * 0.65,
app.viewPortSize.height * 0.3,
] as const
const originSloppy = {
screen: [
app.viewPortSize.width / 2 + 3, // 3px off the center of the screen
app.viewPortSize.height / 2,
],
kcl: [0, 0],
} as const
const xAxisSloppy = {
screen: [
app.viewPortSize.width * 0.75,
app.viewPortSize.height / 2 - 3, // 3px off the X-axis
],
kcl: [16.95, 0],
} as const
const offYAxis = {
screen: [
app.viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range
app.viewPortSize.height * 0.3,
],
kcl: [6.78, 6.78],
} as const
const yAxisSloppy = {
screen: [
app.viewPortSize.width / 2 + 5, // 5px off the Y-axis
app.viewPortSize.height * 0.3,
],
kcl: [0, 6.78],
} as const
const [clickOnXzPlane, moveToXzPlane] = scene.makeMouseHelpers(...xzPlane)
const [clickOriginSloppy] = scene.makeMouseHelpers(...originSloppy.screen)
const [clickXAxisSloppy, moveXAxisSloppy] = scene.makeMouseHelpers(
...xAxisSloppy.screen
)
const [dragToOffYAxis, dragFromOffAxis] = scene.makeDragHelpers(
...offYAxis.screen
)
const expectedCodeSnippets = {
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
segmentOnXAxis: `lineTo([${xAxisSloppy.kcl[0]}, ${xAxisSloppy.kcl[1]}], %)`,
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
}
await app.initialise()
await test.step(`Start a sketch on the XZ plane`, async () => {
await editor.closePane()
await toolbar.startSketchPlaneSelection()
await moveToXzPlane()
await clickOnXzPlane()
// timeout wait for engine animation is unavoidable
await app.page.waitForTimeout(600)
await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane)
})
await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => {
await clickOriginSloppy()
await editor.expectEditor.toContain(expectedCodeSnippets.pointAtOrigin)
})
await test.step(`Add a segment on x-axis after moving the mouse a bit, verify it snaps`, async () => {
await moveXAxisSloppy()
await clickXAxisSloppy()
await editor.expectEditor.toContain(expectedCodeSnippets.segmentOnXAxis)
})
await test.step(`Unequip line tool`, async () => {
await toolbar.lineBtn.click()
await expect(toolbar.lineBtn).not.toHaveAttribute('aria-pressed', 'true')
})
await test.step(`Drag the origin point up and to the right, verify it's past snapping`, async () => {
await dragToOffYAxis({
fromPoint: { x: originSloppy.screen[0], y: originSloppy.screen[1] },
})
await editor.expectEditor.toContain(
expectedCodeSnippets.afterSegmentDraggedOffYAxis
)
})
await test.step(`Drag the origin point left to the y-axis, verify it snaps back`, async () => {
await dragFromOffAxis({
toPoint: { x: yAxisSloppy.screen[0], y: yAxisSloppy.screen[1] },
})
await editor.expectEditor.toContain(
expectedCodeSnippets.afterSegmentDraggedOnYAxis
)
})
})

View File

@ -7,7 +7,7 @@ import {
Paths, Paths,
setupElectron, setupElectron,
tearDown, tearDown,
createProject, createProjectAndRenameIt,
} from './test-utils' } from './test-utils'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import fs from 'fs' import fs from 'fs'
@ -503,261 +503,21 @@ test(
} }
) )
test.describe(`Project management commands`, () => {
test(
`Rename from project page`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const projectName = `my_project_to_rename`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
const u = await getUtils(page)
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `project-000`
// const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const commandContinueButton = page.getByRole('button', {
name: 'Continue',
})
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully renamed`)
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await projectHomeLink.click()
await u.waitForPageLoad()
})
await test.step(`Run rename command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandContinueButton).toBeVisible()
await commandContinueButton.click()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
// TODO: in future I'd like the behavior to be to
// navigate to the new project's page directly,
// see ProjectContextProvider.tsx:158
await test.step(`Check the project was renamed and we navigated home`, async () => {
await expect(projectHomeLink.first()).toBeVisible()
await expect(projectHomeLink.first()).toContainText(projectRenamedName)
})
await electronApp.close()
}
)
test(
`Delete from project page`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const projectName = `my_project_to_delete`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
const u = await getUtils(page)
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'delete project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully deleted`)
const noProjectsMessage = page.getByText('No Projects found')
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await projectHomeLink.click()
await u.waitForPageLoad()
})
await test.step(`Run delete command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandWarning).toBeVisible()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
await test.step(`Check the project was deleted and we navigated home`, async () => {
await expect(noProjectsMessage).toBeVisible()
})
await electronApp.close()
}
)
test(
`Rename from home page`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const projectName = `my_project_to_rename`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `project-000`
const commandContinueButton = page.getByRole('button', {
name: 'Continue',
})
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully renamed`)
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await expect(projectHomeLink).toBeVisible()
})
await test.step(`Run rename command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandContinueButton).toBeVisible()
await commandContinueButton.click()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
await test.step(`Check the project was renamed`, async () => {
await expect(
page.getByRole('link', { name: projectRenamedName })
).toBeVisible()
await expect(projectHomeLink).not.toHaveText(projectName)
})
await electronApp.close()
}
)
test(
`Delete from home page`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const projectName = `my_project_to_delete`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'delete project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully deleted`)
const noProjectsMessage = page.getByText('No Projects found')
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await expect(projectHomeLink).toBeVisible()
})
await test.step(`Run delete command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandWarning).toBeVisible()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
await test.step(`Check the project was deleted`, async () => {
await expect(projectHomeLink).not.toBeVisible()
await expect(noProjectsMessage).toBeVisible()
})
await electronApp.close()
}
)
})
test( test(
'File in the file pane should open with a single click', 'File in the file pane should open with a single click',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ browserName }, testInfo) => {
const projectName = 'router-template-slate'
const { electronApp, page } = await setupElectron({ const { electronApp, page } = await setupElectron({
testInfo, testInfo,
folderSetupFn: async (dir) => { folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true }) await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl', 'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl` `${dir}/router-template-slate/main.kcl`
) )
await fsp.copyFile( await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl', 'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/${projectName}/otherThingToClickOn.kcl` `${dir}/router-template-slate/otherThingToClickOn.kcl`
) )
}, },
}) })
@ -766,7 +526,7 @@ test(
page.on('console', console.log) page.on('console', console.log)
await page.getByText(projectName).click() await page.getByText('router-template-slate').click()
await expect(page.getByTestId('loading')).toBeAttached() await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({ await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000, timeout: 20_000,
@ -883,7 +643,7 @@ test(
page.on('console', console.log) page.on('console', console.log)
await test.step('delete the middle project, i.e. the bracket project', async () => { await test.step('delete the middle project, i.e. the bracket project', async () => {
const project = page.getByTestId('project-link').getByText('bracket') const project = page.getByText('bracket')
await project.hover() await project.hover()
await project.focus() await project.focus()
@ -927,10 +687,10 @@ test(
}) })
await test.step('Check we can still create a project', async () => { await test.step('Check we can still create a project', async () => {
await createProject({ name: 'project-000', page, returnHome: true }) await page.getByRole('button', { name: 'New project' }).click()
await expect( await expect(page.getByText('Successfully created')).toBeVisible()
page.getByTestId('project-link').filter({ hasText: 'project-000' }) await expect(page.getByText('Successfully created')).not.toBeVisible()
).toBeVisible() await expect(page.getByText('project-000')).toBeVisible()
}) })
await electronApp.close() await electronApp.close()
@ -1107,16 +867,17 @@ test.fixme(
const pointOnModel = { x: 660, y: 250 } const pointOnModel = { x: 660, y: 250 }
const expectedStartCamZPosition = 15633.47 const expectedStartCamZPosition = 15633.47
// Constants and locators
const projectLinks = page.getByTestId('project-link')
// 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()
await createProject({ name: 'project-000', page, returnHome: true }) await page.getByRole('button', { name: 'New project' }).click()
await expect(projectLinks.getByText('project-000')).toBeVisible()
await projectLinks.getByText('project-000').click() await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText('project-000')).toBeVisible()
await page.getByText('project-000').click()
await u.waitForPageLoad() await u.waitForPageLoad()
@ -1175,10 +936,16 @@ extrude001 = extrude(200, sketch001)`)
page.getByRole('button', { name: 'New project' }) page.getByRole('button', { name: 'New project' })
).toBeVisible() ).toBeVisible()
const createProject = async (projectNum: number) => {
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
const projectNumStr = projectNum.toString().padStart(3, '0')
await expect(page.getByText(`project-${projectNumStr}`)).toBeVisible()
}
for (let i = 1; i <= 10; i++) { for (let i = 1; i <= 10; i++) {
const name = `project-${i.toString().padStart(3, '0')}` await createProject(i)
await createProject({ name, page, returnHome: true })
await expect(projectLinks.getByText(name)).toBeVisible()
} }
await electronApp.close() await electronApp.close()
} }
@ -1353,10 +1120,11 @@ test(
await page.getByTestId('settings-close-button').click() await page.getByTestId('settings-close-button').click()
await expect(page.getByText('No Projects found')).toBeVisible() await expect(page.getByText('No Projects found')).toBeVisible()
await createProject({ name: 'project-000', page, returnHome: true }) await page.getByRole('button', { name: 'New project' }).click()
await expect( await expect(page.getByText('Successfully created')).toBeVisible()
page.getByTestId('project-link').filter({ hasText: 'project-000' }) await expect(page.getByText('Successfully created')).not.toBeVisible()
).toBeVisible()
await expect(page.getByText(`project-000`)).toBeVisible()
}) })
await test.step('We can change back to the original root project directory', async () => { await test.step('We can change back to the original root project directory', async () => {
@ -1490,13 +1258,13 @@ test(
'function_sketch.kcl', 'function_sketch.kcl',
'function_sketch_with_position.kcl', 'function_sketch_with_position.kcl',
'global-tags.kcl', 'global-tags.kcl',
'helix_ccw.kcl',
'helix_defaults.kcl', 'helix_defaults.kcl',
'helix_defaults_negative_extrude.kcl', 'helix_defaults_negative_extrude.kcl',
'helix_with_length.kcl', 'helix_with_length.kcl',
'i_shape.kcl', 'i_shape.kcl',
'kittycad_svg.kcl', 'kittycad_svg.kcl',
'lego.kcl', 'lego.kcl',
'lsystem.kcl',
'math.kcl', 'math.kcl',
'member_expression_sketch.kcl', 'member_expression_sketch.kcl',
'mike_stress_test.kcl', 'mike_stress_test.kcl',
@ -1682,7 +1450,7 @@ test(
page.on('console', console.log) page.on('console', console.log)
await test.step('Should create and name a project called wrist brace', async () => { await test.step('Should create and name a project called wrist brace', async () => {
await createProject({ name: 'wrist brace', page, returnHome: true }) await createProjectAndRenameIt({ name: 'wrist brace', page })
}) })
await test.step('Should go through onboarding', async () => { await test.step('Should go through onboarding', async () => {

View File

@ -637,6 +637,7 @@ test.describe('Sketch tests', () => {
|> revolve({ axis: "X" }, %)`) |> revolve({ axis: "X" }, %)`)
}) })
test('Can add multiple sketches', async ({ page }) => { test('Can add multiple sketches', async ({ page }) => {
test.skip(process.platform === 'darwin', 'Can add multiple sketches')
const u = await getUtils(page) const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 } const viewportSize = { width: 1200, height: 500 }
await page.setViewportSize(viewportSize) await page.setViewportSize(viewportSize)
@ -674,16 +675,15 @@ test.describe('Sketch tests', () => {
await click00r(50, 0) await click00r(50, 0)
await page.waitForTimeout(100) await page.waitForTimeout(100)
codeStr += ` |> lineTo(${toSU([50, 0])}, %)` codeStr += ` |> line(${toSU([50, 0])}, %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 50) await click00r(0, 50)
codeStr += ` |> line(${toSU([0, 50])}, %)` codeStr += ` |> line(${toSU([0, 50])}, %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
let clickCoords = await click00r(-50, 0) await click00r(-50, 0)
expect(clickCoords).not.toBeUndefined() codeStr += ` |> line(${toSU([-50, 0])}, %)`
codeStr += ` |> lineTo(${toSU(clickCoords!)}, %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
// exit the sketch, reset relative clicker // exit the sketch, reset relative clicker
@ -709,10 +709,8 @@ test.describe('Sketch tests', () => {
codeStr += ` |> startProfileAt([2.03, 0], %)` codeStr += ` |> startProfileAt([2.03, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
// TODO: I couldn't use `toSU` here because of some rounding error causing
// it to be off by 0.01
await click00r(30, 0) await click00r(30, 0)
codeStr += ` |> lineTo([4.07, 0], %)` codeStr += ` |> line([2.04, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 30) await click00r(0, 30)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -219,7 +219,7 @@ test.describe('Test network and connection issues', () => {
|> startProfileAt([12.34, -12.34], %) |> startProfileAt([12.34, -12.34], %)
|> line([12.34, 0], %) |> line([12.34, 0], %)
|> line([-12.34, 12.34], %) |> line([-12.34, 12.34], %)
|> lineTo([0, -12.34], %) |> line([-12.34, 0], %)
`) `)

View File

@ -45,9 +45,7 @@ export const commonPoints = {
startAt: '[7.19, -9.7]', startAt: '[7.19, -9.7]',
num1: 7.25, num1: 7.25,
num2: 14.44, num2: 14.44,
/** The Y-value of a common lineTo move we perform in tests */ }
num3: -2.44,
} as const
/** A semi-reliable color to check the default XZ plane on /** A semi-reliable color to check the default XZ plane on
* in dark mode in the default camera position * in dark mode in the default camera position
@ -120,32 +118,15 @@ async function waitForDefaultPlanesToBeVisible(page: Page) {
) )
} }
export async function checkIfPaneIsOpen(page: Page, testId: string) { async function openPane(page: Page, testId: string) {
const paneButtonLocator = page.getByTestId(testId) const locator = page.getByTestId(testId)
await expect(paneButtonLocator).toBeVisible() await expect(locator).toBeVisible()
return (await paneButtonLocator?.getAttribute('aria-pressed')) === 'true' const isOpen = (await locator?.getAttribute('aria-pressed')) === 'true'
}
export async function openPane(page: Page, testId: string) {
const paneButtonLocator = page.getByTestId(testId)
await expect(paneButtonLocator).toBeVisible()
const isOpen = await checkIfPaneIsOpen(page, testId)
if (!isOpen) { if (!isOpen) {
await paneButtonLocator.click() await locator.click()
await expect(locator).toHaveAttribute('aria-pressed', 'true')
} }
await expect(paneButtonLocator).toHaveAttribute('aria-pressed', 'true')
}
export async function closePane(page: Page, testId: string) {
const paneButtonLocator = page.getByTestId(testId)
await expect(paneButtonLocator).toBeVisible()
const isOpen = await checkIfPaneIsOpen(page, testId)
if (isOpen) {
await paneButtonLocator.click()
}
await expect(paneButtonLocator).toHaveAttribute('aria-pressed', 'false')
} }
async function openKclCodePanel(page: Page) { async function openKclCodePanel(page: Page) {
@ -486,6 +467,20 @@ export async function getUtils(page: Page, test_?: typeof test) {
return text.replace(/\s+/g, '') return text.replace(/\s+/g, '')
}, },
createAndSelectProject: async (hasText: string) => {
return test_?.step(
`Create and select project with text "${hasText}"`,
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()
const projectLinksPost = page.getByTestId('project-link')
await projectLinksPost.filter({ hasText }).click()
}
)
},
editorTextMatches: async (code: string) => { editorTextMatches: async (code: string) => {
const editor = page.locator(editorSelector) const editor = page.locator(editorSelector)
return expect(editor).toHaveText(code, { useInnerText: true }) return expect(editor).toHaveText(code, { useInnerText: true })
@ -525,9 +520,6 @@ export async function getUtils(page: Page, test_?: typeof test) {
.locator('[data-testid="file-pane-scroll-container"] button') .locator('[data-testid="file-pane-scroll-container"] button')
.filter({ hasText: name }) .filter({ hasText: name })
.click() .click()
await expect(page.getByTestId('project-sidebar-toggle')).toContainText(
name
)
}) })
}, },
@ -896,17 +888,7 @@ export async function setupElectron({
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
const settingsOverrides = TOML.stringify( const settingsOverrides = TOML.stringify(
appSettings appSettings
? { ? { settings: appSettings }
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
projectDirectory: projectDirName,
...appSettings.app,
},
},
}
: { : {
settings: { settings: {
...TEST_SETTINGS, ...TEST_SETTINGS,
@ -988,25 +970,30 @@ export async function isOutOfViewInScrollContainer(
return isOutOfView return isOutOfView
} }
export async function createProject({ export async function createProjectAndRenameIt({
name, name,
page, page,
returnHome = false,
}: { }: {
name: string name: string
page: Page page: Page
returnHome?: boolean
}) { }) {
await test.step(`Create project and navigate to it`, async () => { await page.getByRole('button', { name: 'New project' }).click()
await page.getByRole('button', { name: 'New project' }).click() await expect(page.getByText('Successfully created')).toBeVisible()
await page.getByRole('textbox', { name: 'Name' }).fill(name) await expect(page.getByText('Successfully created')).not.toBeVisible()
await page.getByRole('button', { name: 'Continue' }).click()
if (returnHome) { await expect(page.getByText(`project-000`)).toBeVisible()
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) await page.getByText(`project-000`).hover()
await page.getByTestId('app-logo').click() await page.getByText(`project-000`).focus()
}
}) await page.getByLabel('sketch').first().click()
await page.waitForTimeout(100)
// type the name passed in
await page.keyboard.press('Backspace')
await page.keyboard.type(name)
await page.getByLabel('checkmark').last().click()
} }
export function executorInputPath(fileName: string): string { export function executorInputPath(fileName: string): string {

View File

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

View File

@ -96,7 +96,7 @@ test.describe('Testing selections', () => {
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %) |> line([0, ${commonPoints.num1 + 0.01}], %)
|> lineTo([0, ${commonPoints.num3}], %)`) |> line([-${commonPoints.num2}, 0], %)`)
// deselect line tool // deselect line tool
await page.getByRole('button', { name: 'line Line', exact: true }).click() await page.getByRole('button', { name: 'line Line', exact: true }).click()
@ -157,9 +157,7 @@ test.describe('Testing selections', () => {
await emptySpaceClick() await emptySpaceClick()
// check the same selection again by putting cursor in code first then selecting axis // check the same selection again by putting cursor in code first then selecting axis
await page await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`)
.click()
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
await constrainButton.click() await constrainButton.click()
await expect(absYButton).toBeDisabled() await expect(absYButton).toBeDisabled()
@ -182,9 +180,7 @@ test.describe('Testing selections', () => {
process.platform === 'linux' ? 'Control' : 'Meta' process.platform === 'linux' ? 'Control' : 'Meta'
) )
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`)
.click()
await expect(page.locator('.cm-cursor')).toHaveCount(2) await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.waitForTimeout(500) await page.waitForTimeout(500)

View File

@ -7,7 +7,6 @@ import {
setupElectron, setupElectron,
tearDown, tearDown,
executorInputPath, executorInputPath,
createProject,
} from './test-utils' } from './test-utils'
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes' import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants' import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
@ -266,15 +265,10 @@ test.describe('Testing settings', () => {
process.platform === 'win32', process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557' 'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
) )
const projectName = 'bracket' const { electronApp, page } = await setupElectron({
const {
electronApp,
page,
dir: projectDirName,
} = await setupElectron({
testInfo, testInfo,
folderSetupFn: async (dir) => { folderSetupFn: async (dir) => {
const bracketDir = join(dir, projectName) const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
@ -286,12 +280,6 @@ test.describe('Testing settings', () => {
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
// Selectors and constants // Selectors and constants
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
const tempUserSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
const userThemeColor = '120' const userThemeColor = '120'
const projectThemeColor = '50' const projectThemeColor = '50'
const settingsOpenButton = page.getByRole('link', { const settingsOpenButton = page.getByRole('link', {
@ -312,13 +300,6 @@ test.describe('Testing settings', () => {
await themeColorSetting.fill(userThemeColor) await themeColorSetting.fill(userThemeColor)
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor) await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
await settingsCloseButton.click() await settingsCloseButton.click()
await expect
.poll(async () => fsp.readFile(tempUserSettingsFilePath, 'utf-8'), {
message: 'Setting should now be written to the file',
timeout: 5_000,
})
.toContain(`themeColor = "${userThemeColor}"`)
// Only close the button after we've confirmed
}) })
await test.step('Set project theme color', async () => { await test.step('Set project theme color', async () => {
@ -330,23 +311,13 @@ test.describe('Testing settings', () => {
await themeColorSetting.fill(projectThemeColor) await themeColorSetting.fill(projectThemeColor)
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor) await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
await settingsCloseButton.click() await settingsCloseButton.click()
// Make sure that the project settings file has been written to before continuing
await expect
.poll(
async () => fsp.readFile(tempProjectSettingsFilePath, 'utf-8'),
{
message: 'Setting should now be written to the file',
timeout: 5_000,
}
)
.toContain(`themeColor = "${projectThemeColor}"`)
}) })
await test.step('Refresh the application and see project setting applied', async () => { await test.step('Refresh the application and see project setting applied', async () => {
// Make sure we're done navigating before we reload // Make sure we're done navigating before we reload
await expect(settingsCloseButton).not.toBeVisible() await expect(settingsCloseButton).not.toBeVisible()
await page.reload({ waitUntil: 'domcontentloaded' }) await page.reload({ waitUntil: 'domcontentloaded' })
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor) await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
}) })
@ -457,7 +428,9 @@ test.describe('Testing settings', () => {
}) })
await test.step('Check color of logo changed when in modeling view', async () => { await test.step('Check color of logo changed when in modeling view', async () => {
await createProject({ name: 'project-000', page }) await page.getByRole('button', { name: 'New project' }).click()
await page.getByTestId('project-link').first().click()
await page.getByRole('button', { name: 'Dismiss' }).click()
await changeColor('58') await changeColor('58')
await expect(logoLink).toHaveCSS('--primary-hue', '58') await expect(logoLink).toHaveCSS('--primary-hue', '58')
}) })
@ -475,7 +448,7 @@ test.describe('Testing settings', () => {
test( test(
'project settings reload on external change', 'project settings reload on external change',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName: _ }, testInfo) => { async ({ browserName }, testInfo) => {
const { const {
electronApp, electronApp,
page, page,
@ -493,7 +466,11 @@ test.describe('Testing settings', () => {
await expect(projectDirLink).toBeVisible() await expect(projectDirLink).toBeVisible()
}) })
await createProject({ name: 'project-000', page }) 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 changeColorFs = async (color: string) => {
const tempSettingsFilePath = join( const tempSettingsFilePath = join(

View File

@ -1,11 +1,5 @@
import { test, expect, Page } from '@playwright/test' import { test, expect, Page } from '@playwright/test'
import { import { getUtils, setup, tearDown, setupElectron } from './test-utils'
getUtils,
setup,
tearDown,
setupElectron,
createProject,
} from './test-utils'
import { join } from 'path' import { join } from 'path'
import fs from 'fs' import fs from 'fs'
@ -706,17 +700,17 @@ test(
const fileExists = () => const fileExists = () =>
fs.existsSync(join(dir, projectName, textToCadFileName)) fs.existsSync(join(dir, projectName, textToCadFileName))
const { openFilePanel, openKclCodePanel, waitForPageLoad } = await getUtils( const {
page, createAndSelectProject,
test openFilePanel,
) openKclCodePanel,
waitForPageLoad,
} = await getUtils(page, test)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
// Locators // Locators
const projectMenuButton = page const projectMenuButton = page.getByRole('button', { name: projectName })
.getByTestId('project-sidebar-toggle')
.filter({ hasText: projectName })
const textToCadFileButton = page.getByRole('listitem').filter({ const textToCadFileButton = page.getByRole('listitem').filter({
has: page.getByRole('button', { name: textToCadFileName }), has: page.getByRole('button', { name: textToCadFileName }),
}) })
@ -725,7 +719,7 @@ test(
) )
// Create and navigate to the project // Create and navigate to the project
await createProject({ name: 'project-000', page }) await createAndSelectProject('project-000')
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command // Wait for Start Sketch otherwise you will not have access Text-to-CAD command
await waitForPageLoad() await waitForPageLoad()

2
interface.d.ts vendored
View File

@ -2,7 +2,7 @@ import fs from 'node:fs/promises'
import fsSync from 'node:fs' import fsSync from 'node:fs'
import path from 'path' import path from 'path'
import { dialog, shell } from 'electron' import { dialog, shell } from 'electron'
import { MachinesListing } from 'components/MachineManagerProvider' import { MachinesListing } from 'lib/machineManager'
type EnvFn = (value?: string) => string type EnvFn = (value?: string) => string

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "zoo-modeling-app", "name": "zoo-modeling-app",
"version": "0.26.2", "version": "0.26.0",
"private": true, "private": true,
"productName": "Zoo Modeling App", "productName": "Zoo Modeling App",
"author": { "author": {
@ -56,7 +56,7 @@
"react-json-view": "^1.21.3", "react-json-view": "^1.21.3",
"react-modal": "^3.16.1", "react-modal": "^3.16.1",
"react-modal-promise": "^1.0.2", "react-modal-promise": "^1.0.2",
"react-router-dom": "^6.27.0", "react-router-dom": "^6.26.1",
"sketch-helpers": "^0.0.4", "sketch-helpers": "^0.0.4",
"three": "^0.166.1", "three": "^0.166.1",
"ua-parser-js": "^1.0.37", "ua-parser-js": "^1.0.37",
@ -76,7 +76,7 @@
"build:both": "vite build", "build:both": "vite build",
"build:both:local": "yarn build:wasm && vite build", "build:both:local": "yarn build:wasm && vite build",
"pretest": "yarn remove-importmeta", "pretest": "yarn remove-importmeta",
"test:rust": "(cd src/wasm-lib && cargo test --workspace && cargo clippy --workspace --all-targets)", "test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000", "simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &", "simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &", "simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
@ -161,7 +161,7 @@
"@types/isomorphic-fetch": "^0.0.39", "@types/isomorphic-fetch": "^0.0.39",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/mocha": "^10.0.6", "@types/mocha": "^10.0.6",
"@types/node": "^22.7.8", "@types/node": "^22.5.0",
"@types/pixelmatch": "^5.2.6", "@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/react": "^18.3.4", "@types/react": "^18.3.4",

View File

@ -34,11 +34,6 @@
"title": "Car Wheel Assembly", "title": "Car Wheel Assembly",
"description": "A car wheel assembly with a rotor, tire, and lug nuts." "description": "A car wheel assembly with a rotor, tire, and lug nuts."
}, },
{
"file": "dodecahedron.kcl",
"title": "Hollow Dodecahedron",
"description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the individual faces of the dodecahedron and extruding inwards."
},
{ {
"file": "enclosure.kcl", "file": "enclosure.kcl",
"title": "Enclosure", "title": "Enclosure",
@ -59,11 +54,6 @@
"title": "A mounting bracket for the Focusrite Scarlett Solo audio interface", "title": "A mounting bracket for the Focusrite Scarlett Solo audio interface",
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material" "description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material"
}, },
{
"file": "food-service-spatula.kcl",
"title": "Food Service Spatula",
"description": "Use these spatulas for mixing, flipping, and scraping."
},
{ {
"file": "french-press.kcl", "file": "french-press.kcl",
"title": "French Press", "title": "French Press",
@ -71,7 +61,7 @@
}, },
{ {
"file": "gear.kcl", "file": "gear.kcl",
"title": "Spur Gear", "title": "Gear",
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear." "description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear."
}, },
{ {

View File

View File

@ -21,7 +21,6 @@ import { WasmErrBanner } from 'components/WasmErrBanner'
import { CommandBar } from 'components/CommandBar/CommandBar' import { CommandBar } from 'components/CommandBar/CommandBar'
import ModelingMachineProvider from 'components/ModelingMachineProvider' import ModelingMachineProvider from 'components/ModelingMachineProvider'
import FileMachineProvider from 'components/FileMachineProvider' import FileMachineProvider from 'components/FileMachineProvider'
import { MachineManagerProvider } from 'components/MachineManagerProvider'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { import {
fileLoader, fileLoader,
@ -43,7 +42,6 @@ import { coreDump } from 'lang/wasm'
import { useMemo } from 'react' import { useMemo } from 'react'
import { AppStateProvider } from 'AppState' import { AppStateProvider } from 'AppState'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
@ -51,22 +49,17 @@ const router = createRouter([
{ {
loader: settingsLoader, loader: settingsLoader,
id: PATHS.INDEX, id: PATHS.INDEX,
// TODO: Re-evaluate if this is true
/* Make sure auth is the outermost provider or else we will have /* Make sure auth is the outermost provider or else we will have
* inefficient re-renders, use the react profiler to see. */ * inefficient re-renders, use the react profiler to see. */
element: ( element: (
<CommandBarProvider> <CommandBarProvider>
<SettingsAuthProvider> <SettingsAuthProvider>
<LspProvider> <LspProvider>
<ProjectsContextProvider> <KclContextProvider>
<KclContextProvider> <AppStateProvider>
<AppStateProvider> <Outlet />
<MachineManagerProvider> </AppStateProvider>
<Outlet /> </KclContextProvider>
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</ProjectsContextProvider>
</LspProvider> </LspProvider>
</SettingsAuthProvider> </SettingsAuthProvider>
</CommandBarProvider> </CommandBarProvider>

View File

@ -100,11 +100,6 @@ export function Toolbar({
function resolveItemConfig( function resolveItemConfig(
maybeIconConfig: ToolbarItem maybeIconConfig: ToolbarItem
): ToolbarItemResolved { ): ToolbarItemResolved {
const isDisabled =
disableAllButtons ||
maybeIconConfig.status !== 'available' ||
maybeIconConfig.disabled?.(state) === true
return { return {
...maybeIconConfig, ...maybeIconConfig,
title: title:
@ -118,11 +113,10 @@ export function Toolbar({
typeof maybeIconConfig.hotkey === 'string' typeof maybeIconConfig.hotkey === 'string'
? maybeIconConfig.hotkey ? maybeIconConfig.hotkey
: maybeIconConfig.hotkey?.(state), : maybeIconConfig.hotkey?.(state),
disabled: isDisabled, disabled:
disabledReason: disableAllButtons ||
typeof maybeIconConfig.disabledReason === 'function' maybeIconConfig.status !== 'available' ||
? maybeIconConfig.disabledReason(state) maybeIconConfig.disabled?.(state) === true,
: maybeIconConfig.disabledReason,
disableHotkey: maybeIconConfig.disableHotkey?.(state), disableHotkey: maybeIconConfig.disableHotkey?.(state),
status: maybeIconConfig.status, status: maybeIconConfig.status,
} }
@ -279,8 +273,6 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
itemConfig: ToolbarItemResolved itemConfig: ToolbarItemResolved
configCallbackProps: ToolbarItemCallbackProps configCallbackProps: ToolbarItemCallbackProps
}) { }) {
const { state } = useModelingContext()
useHotkeys( useHotkeys(
itemConfig.hotkey || '', itemConfig.hotkey || '',
() => { () => {
@ -344,17 +336,6 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
)} )}
</div> </div>
<p className="px-2 text-ch font-sans">{itemConfig.description}</p> <p className="px-2 text-ch font-sans">{itemConfig.description}</p>
{/* Add disabled reason if item is disabled */}
{itemConfig.disabled && itemConfig.disabledReason && (
<>
<hr className="border-chalkboard-20 dark:border-chalkboard-80" />
<p className="px-2 text-ch font-sans text-chalkboard-70 dark:text-chalkboard-40">
{typeof itemConfig.disabledReason === 'function'
? itemConfig.disabledReason(state)
: itemConfig.disabledReason}
</p>
</>
)}
{itemConfig.links.length > 0 && ( {itemConfig.links.length > 0 && (
<> <>
<hr className="border-chalkboard-20 dark:border-chalkboard-80" /> <hr className="border-chalkboard-20 dark:border-chalkboard-80" />

View File

@ -92,7 +92,6 @@ export class CameraControls {
target: Vector3 target: Vector3
domElement: HTMLCanvasElement domElement: HTMLCanvasElement
isDragging: boolean isDragging: boolean
wasDragging: boolean
mouseDownPosition: Vector2 mouseDownPosition: Vector2
mouseNewPosition: Vector2 mouseNewPosition: Vector2
rotationSpeed = 0.3 rotationSpeed = 0.3
@ -234,7 +233,6 @@ export class CameraControls {
this.target = new Vector3() this.target = new Vector3()
this.domElement = domElement this.domElement = domElement
this.isDragging = false this.isDragging = false
this.wasDragging = false
this.mouseDownPosition = new Vector2() this.mouseDownPosition = new Vector2()
this.mouseNewPosition = new Vector2() this.mouseNewPosition = new Vector2()
@ -365,8 +363,6 @@ export class CameraControls {
onMouseDown = (event: PointerEvent) => { onMouseDown = (event: PointerEvent) => {
this.domElement.setPointerCapture(event.pointerId) this.domElement.setPointerCapture(event.pointerId)
this.isDragging = true this.isDragging = true
// Reset the wasDragging flag to false when starting a new drag
this.wasDragging = false
this.mouseDownPosition.set(event.clientX, event.clientY) this.mouseDownPosition.set(event.clientX, event.clientY)
let interaction = this.getInteractionType(event) let interaction = this.getInteractionType(event)
if (interaction === 'none') return if (interaction === 'none') return
@ -396,10 +392,6 @@ export class CameraControls {
const interaction = this.getInteractionType(event) const interaction = this.getInteractionType(event)
if (interaction === 'none') return if (interaction === 'none') return
// If there's a valid interaction and the mouse is moving,
// our past (and current) interaction was a drag.
this.wasDragging = true
if (this.syncDirection === 'engineToClient') { if (this.syncDirection === 'engineToClient') {
this.moveSender.send(() => { this.moveSender.send(() => {
this.doMove(interaction, [event.clientX, event.clientY]) this.doMove(interaction, [event.clientX, event.clientY])
@ -407,7 +399,6 @@ export class CameraControls {
return return
} }
// else "clientToEngine" (Sketch Mode) or forceUpdate
// Implement camera movement logic here based on deltaMove // Implement camera movement logic here based on deltaMove
// For example, for rotating the camera around the target: // For example, for rotating the camera around the target:
if (interaction === 'rotate') { if (interaction === 'rotate') {
@ -436,9 +427,6 @@ export class CameraControls {
* under the cursor. This recently moved from being handled in App.tsx. * under the cursor. This recently moved from being handled in App.tsx.
* This might not be the right spot, but it is more consolidated. * This might not be the right spot, but it is more consolidated.
*/ */
// Clear any previous drag state
this.wasDragging = false
if (this.syncDirection === 'engineToClient') { if (this.syncDirection === 'engineToClient') {
const newCmdId = uuidv4() const newCmdId = uuidv4()

View File

@ -44,7 +44,6 @@ import {
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
import { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { Node } from 'wasm-lib/kcl/bindings/Node'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } { function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false) const [isCamMoving, setIsCamMoving] = useState(false)
@ -202,7 +201,7 @@ const Overlay = ({
let xAlignment = overlay.angle < 0 ? '0%' : '-100%' let xAlignment = overlay.angle < 0 ? '0%' : '-100%'
let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '0%' : '-100%' let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '0%' : '-100%'
const _node1 = getNodeFromPath<Node<CallExpression>>( const _node1 = getNodeFromPath<CallExpression>(
kclManager.ast, kclManager.ast,
overlay.pathToNode, overlay.pathToNode,
'CallExpression' 'CallExpression'
@ -382,7 +381,7 @@ export async function deleteSegment({
pathToNode: PathToNode pathToNode: PathToNode
sketchDetails: SketchDetails | null sketchDetails: SketchDetails | null
}) { }) {
let modifiedAst: Node<Program> | Error = kclManager.ast let modifiedAst: Program | Error = kclManager.ast
const dependentRanges = findUsesOfTagInPipe(modifiedAst, pathToNode) const dependentRanges = findUsesOfTagInPipe(modifiedAst, pathToNode)
const shouldContinueSegDelete = dependentRanges.length const shouldContinueSegDelete = dependentRanges.length

View File

@ -19,8 +19,6 @@ import {
import { import {
ARROWHEAD, ARROWHEAD,
AXIS_GROUP, AXIS_GROUP,
DRAFT_POINT,
DRAFT_POINT_GROUP,
getSceneScale, getSceneScale,
INTERSECTION_PLANE_LAYER, INTERSECTION_PLANE_LAYER,
OnClickCallbackArgs, OnClickCallbackArgs,
@ -55,7 +53,7 @@ import {
editorManager, editorManager,
} from 'lib/singletons' } from 'lib/singletons'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst, ToolTip } from 'lang/langHelpers' import { executeAst } from 'lang/langHelpers'
import { import {
createProfileStartHandle, createProfileStartHandle,
SegmentUtils, SegmentUtils,
@ -94,7 +92,6 @@ import { err, reportRejection, trap } from 'lib/trap'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d' import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
import { SegmentInputs } from 'lang/std/stdTypes' import { SegmentInputs } from 'lang/std/stdTypes'
import { Node } from 'wasm-lib/kcl/bindings/Node'
type DraftSegment = 'line' | 'tangentialArcTo' type DraftSegment = 'line' | 'tangentialArcTo'
@ -316,27 +313,6 @@ export class SceneEntities {
const intersectionPlane = this.scene.getObjectByName(RAYCASTABLE_PLANE) const intersectionPlane = this.scene.getObjectByName(RAYCASTABLE_PLANE)
if (intersectionPlane) this.scene.remove(intersectionPlane) if (intersectionPlane) this.scene.remove(intersectionPlane)
} }
getDraftPoint() {
return this.scene.getObjectByName(DRAFT_POINT)
}
createDraftPoint({ point, group }: { point: Vector2; group: Group }) {
const dummy = new Mesh()
dummy.position.set(0, 0, 0)
const scale = sceneInfra.getClientSceneScaleFactor(dummy)
const draftPoint = createProfileStartHandle({
isDraft: true,
from: [point.x, point.y],
scale,
theme: sceneInfra._theme,
})
draftPoint.layers.set(SKETCH_LAYER)
group.add(draftPoint)
}
removeDraftPoint() {
const draftPoint = this.getDraftPoint()
if (draftPoint) draftPoint.removeFromParent()
}
setupNoPointsListener({ setupNoPointsListener({
sketchDetails, sketchDetails,
@ -345,106 +321,30 @@ export class SceneEntities {
sketchDetails: SketchDetails sketchDetails: SketchDetails
afterClick: (args: OnClickCallbackArgs) => void afterClick: (args: OnClickCallbackArgs) => void
}) { }) {
// TODO: Consolidate shared logic between this and setupSketch // Create a THREEjs plane to raycast clicks onto
// Which should just fire when the sketch mode is entered,
// instead of in these two separate XState states.
this.createIntersectionPlane() this.createIntersectionPlane()
const draftPointGroup = new Group()
draftPointGroup.name = DRAFT_POINT_GROUP
sketchDetails.origin &&
draftPointGroup.position.set(...sketchDetails.origin)
if (!(sketchDetails.yAxis && sketchDetails)) {
console.error('No sketch quaternion or sketch details found')
return
}
this.currentSketchQuaternion = quaternionFromUpNForward(
new Vector3(...sketchDetails.yAxis),
new Vector3(...sketchDetails.zAxis)
)
draftPointGroup.setRotationFromQuaternion(this.currentSketchQuaternion)
this.scene.add(draftPointGroup)
const quaternion = quaternionFromUpNForward( const quaternion = quaternionFromUpNForward(
new Vector3(...sketchDetails.yAxis), new Vector3(...sketchDetails.yAxis),
new Vector3(...sketchDetails.zAxis) new Vector3(...sketchDetails.zAxis)
) )
// Position the click raycast plane // Position the click raycast plane
this.intersectionPlane!.setRotationFromQuaternion(quaternion) if (this.intersectionPlane) {
this.intersectionPlane!.position.copy( this.intersectionPlane.setRotationFromQuaternion(quaternion)
new Vector3(...(sketchDetails?.origin || [0, 0, 0])) this.intersectionPlane.position.copy(
) new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
)
}
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onMove: (args) => {
if (!args.intersects.length) return
const axisIntersection = args.intersects.find(
(sceneObject) =>
sceneObject.object.name === X_AXIS ||
sceneObject.object.name === Y_AXIS
)
if (!axisIntersection) return
const { intersectionPoint } = args
// We're hovering over an axis, so we should show a draft point
const snappedPoint = intersectionPoint.twoD.clone()
if (axisIntersection.object.name === X_AXIS) {
snappedPoint.setComponent(1, 0)
} else {
snappedPoint.setComponent(0, 0)
}
// Either create a new one or update the existing one
const draftPoint = this.getDraftPoint()
if (!draftPoint) {
this.createDraftPoint({
point: snappedPoint,
group: draftPointGroup,
})
} else {
// Ignore if there are huge jumps in the mouse position,
// that is likely a strange behavior
if (
draftPoint.position.distanceTo(
new Vector3(snappedPoint.x, snappedPoint.y, 0)
) > 100
) {
return
}
draftPoint.position.set(snappedPoint.x, snappedPoint.y, 0)
}
},
onMouseLeave: () => {
this.removeDraftPoint()
},
onClick: async (args) => { onClick: async (args) => {
this.removeDraftPoint()
if (!args) return if (!args) return
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args.mouseEvent.which !== 1) return if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args const { intersectionPoint } = args
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return
// Snap to either or both axes
// if the click intersects their meshes
const yAxisIntersection = args.intersects.find(
(sceneObject) => sceneObject.object.name === Y_AXIS
)
const xAxisIntersection = args.intersects.find(
(sceneObject) => sceneObject.object.name === X_AXIS
)
const snappedClickPoint = {
x: yAxisIntersection ? 0 : intersectionPoint.twoD.x,
y: xAxisIntersection ? 0 : intersectionPoint.twoD.y,
}
const addStartProfileAtRes = addStartProfileAt( const addStartProfileAtRes = addStartProfileAt(
kclManager.ast, kclManager.ast,
sketchDetails.sketchPathToNode, sketchDetails.sketchPathToNode,
[snappedClickPoint.x, snappedClickPoint.y] [intersectionPoint.twoD.x, intersectionPoint.twoD.y]
) )
if (trap(addStartProfileAtRes)) return if (trap(addStartProfileAtRes)) return
@ -452,7 +352,6 @@ export class SceneEntities {
await kclManager.updateAst(modifiedAst, false) await kclManager.updateAst(modifiedAst, false)
this.removeIntersectionPlane() this.removeIntersectionPlane()
this.scene.remove(draftPointGroup)
// Now perform the caller-specified action // Now perform the caller-specified action
afterClick(args) afterClick(args)
@ -470,14 +369,14 @@ export class SceneEntities {
selectionRanges, selectionRanges,
}: { }: {
sketchPathToNode: PathToNode sketchPathToNode: PathToNode
maybeModdedAst: Node<Program> maybeModdedAst: Program
draftExpressionsIndices?: { start: number; end: number } draftExpressionsIndices?: { start: number; end: number }
forward: [number, number, number] forward: [number, number, number]
up: [number, number, number] up: [number, number, number]
position?: [number, number, number] position?: [number, number, number]
selectionRanges?: Selections selectionRanges?: Selections
}): Promise<{ }): Promise<{
truncatedAst: Node<Program> truncatedAst: Program
programMemoryOverride: ProgramMemory programMemoryOverride: ProgramMemory
sketch: Sketch sketch: Sketch
variableDeclarationName: string variableDeclarationName: string
@ -508,7 +407,7 @@ export class SceneEntities {
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
if (!sketch) return Promise.reject('sketch not found') if (!sketch) return Promise.reject('sketch not found')
if (!isArray(sketch?.paths)) if (!isArray(sketch?.value))
return { return {
truncatedAst, truncatedAst,
programMemoryOverride, programMemoryOverride,
@ -525,20 +424,24 @@ export class SceneEntities {
const dummy = new Mesh() const dummy = new Mesh()
// TODO: When we actually have sketch positions and rotations we can use them here. // TODO: When we actually have sketch positions and rotations we can use them here.
dummy.position.set(0, 0, 0) dummy.position.set(0, 0, 0)
const scale = sceneInfra.getClientSceneScaleFactor(dummy) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, dummy)) /
sceneInfra._baseUnitMultiplier
const segPathToNode = getNodePathFromSourceRange( const segPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
sketch.start.__geoMeta.sourceRange sketch.start.__geoMeta.sourceRange
) )
if (sketch?.paths?.[0]?.type !== 'Circle') { if (sketch?.value?.[0]?.type !== 'Circle') {
const _profileStart = createProfileStartHandle({ const _profileStart = createProfileStartHandle({
from: sketch.start.from, from: sketch.start.from,
id: sketch.start.__geoMeta.id, id: sketch.start.__geoMeta.id,
pathToNode: segPathToNode, pathToNode: segPathToNode,
scale, scale: factor,
theme: sceneInfra._theme, theme: sceneInfra._theme,
isDraft: false,
}) })
_profileStart.layers.set(SKETCH_LAYER) _profileStart.layers.set(SKETCH_LAYER)
_profileStart.traverse((child) => { _profileStart.traverse((child) => {
@ -548,16 +451,16 @@ export class SceneEntities {
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
} }
const callbacks: (() => SegmentOverlayPayload | null)[] = [] const callbacks: (() => SegmentOverlayPayload | null)[] = []
sketch.paths.forEach((segment, index) => { sketch.value.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange( let segPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
segment.__geoMeta.sourceRange segment.__geoMeta.sourceRange
) )
if ( if (
draftExpressionsIndices && draftExpressionsIndices &&
(sketch.paths[index - 1] || sketch.start) (sketch.value[index - 1] || sketch.start)
) { ) {
const previousSegment = sketch.paths[index - 1] || sketch.start const previousSegment = sketch.value[index - 1] || sketch.start
const previousSegmentPathToNode = getNodePathFromSourceRange( const previousSegmentPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
previousSegment.__geoMeta.sourceRange previousSegment.__geoMeta.sourceRange
@ -608,13 +511,13 @@ export class SceneEntities {
to: segment.to, to: segment.to,
} }
const result = initSegment({ const result = initSegment({
prevSegment: sketch.paths[index - 1], prevSegment: sketch.value[index - 1],
callExpName, callExpName,
input, input,
id: segment.__geoMeta.id, id: segment.__geoMeta.id,
pathToNode: segPathToNode, pathToNode: segPathToNode,
isDraftSegment, isDraftSegment,
scale, scale: factor,
texture: sceneInfra.extraSegmentTexture, texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme, theme: sceneInfra._theme,
isSelected, isSelected,
@ -658,7 +561,7 @@ export class SceneEntities {
} }
updateAstAndRejigSketch = async ( updateAstAndRejigSketch = async (
sketchPathToNode: PathToNode, sketchPathToNode: PathToNode,
modifiedAst: Node<Program> | Error, modifiedAst: Program | Error,
forward: [number, number, number], forward: [number, number, number],
up: [number, number, number], up: [number, number, number],
origin: [number, number, number] origin: [number, number, number]
@ -707,9 +610,9 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return Promise.reject(sg) if (err(sg)) return Promise.reject(sg)
const lastSeg = sg?.paths?.slice(-1)[0] || sg.start const lastSeg = sg?.value?.slice(-1)[0] || sg.start
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1` const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: _ast, node: _ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -742,24 +645,16 @@ export class SceneEntities {
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onClick: async (args) => { onClick: async (args) => {
if (!args) return if (!args) return
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args.mouseEvent.which !== 1) return if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args const { intersectionPoint } = args
let intersection2d = intersectionPoint?.twoD let intersection2d = intersectionPoint?.twoD
const intersectsProfileStart = args.intersects const profileStart = args.intersects
.map(({ object }) => getParentGroup(object, [PROFILE_START])) .map(({ object }) => getParentGroup(object, [PROFILE_START]))
.find((a) => a?.name === PROFILE_START) .find((a) => a?.name === PROFILE_START)
let modifiedAst let modifiedAst
if (profileStart) {
// Snapping logic for the profile start handle const lastSegment = sketch.value.slice(-1)[0]
if (intersectsProfileStart) {
const lastSegment = sketch.paths.slice(-1)[0]
modifiedAst = addCallExpressionsToPipe({ modifiedAst = addCallExpressionsToPipe({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -791,39 +686,19 @@ export class SceneEntities {
}) })
if (trap(modifiedAst)) return Promise.reject(modifiedAst) if (trap(modifiedAst)) return Promise.reject(modifiedAst)
} else if (intersection2d) { } else if (intersection2d) {
const intersectsYAxis = args.intersects.find( const lastSegment = sketch.value.slice(-1)[0]
(sceneObject) => sceneObject.object.name === Y_AXIS
)
const intersectsXAxis = args.intersects.find(
(sceneObject) => sceneObject.object.name === X_AXIS
)
const lastSegment = sketch.paths.slice(-1)[0]
const snappedPoint = {
x: intersectsYAxis ? 0 : intersection2d.x,
y: intersectsXAxis ? 0 : intersection2d.y,
}
let resolvedFunctionName: ToolTip = 'line'
// This might need to become its own function if we want more
// case-based logic for different segment types
if (lastSegment.type === 'TangentialArcTo') {
resolvedFunctionName = 'tangentialArcTo'
} else if (snappedPoint.x === 0 || snappedPoint.y === 0) {
// We consider a point placed on axes or origin to be absolute
resolvedFunctionName = 'lineTo'
}
const tmp = addNewSketchLn({ const tmp = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
input: { input: {
type: 'straight-segment', type: 'straight-segment',
from: [lastSegment.to[0], lastSegment.to[1]], from: [lastSegment.to[0], lastSegment.to[1]],
to: [snappedPoint.x, snappedPoint.y], to: [intersection2d.x, intersection2d.y],
}, },
fnName: resolvedFunctionName, fnName:
lastSegment.type === 'TangentialArcTo'
? 'tangentialArcTo'
: 'line',
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
}) })
if (trap(tmp)) return Promise.reject(tmp) if (trap(tmp)) return Promise.reject(tmp)
@ -835,7 +710,7 @@ export class SceneEntities {
} }
await kclManager.executeAstMock(modifiedAst) await kclManager.executeAstMock(modifiedAst)
if (intersectsProfileStart) { if (profileStart) {
sceneInfra.modelingSend({ type: 'CancelSketch' }) sceneInfra.modelingSend({ type: 'CancelSketch' })
} else { } else {
await this.setUpDraftSegment( await this.setUpDraftSegment(
@ -860,6 +735,7 @@ export class SceneEntities {
}, },
}) })
}, },
...this.mouseEnterLeaveCallbacks(),
}) })
} }
setupDraftRectangle = async ( setupDraftRectangle = async (
@ -941,7 +817,7 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
const sgPaths = sketch.paths const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch) this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
@ -950,11 +826,6 @@ export class SceneEntities {
) )
}, },
onClick: async (args) => { onClick: async (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
// Commit the rectangle to the full AST/code and return to sketch.idle // Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return if (!cornerPoint || args.mouseEvent.button !== 0) return
@ -997,7 +868,7 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return if (err(sketch)) return
const sgPaths = sketch.paths const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
// Update the starting segment of the THREEjs scene // Update the starting segment of the THREEjs scene
@ -1114,7 +985,7 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return if (err(sketch)) return
const sgPaths = sketch.paths const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch) this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
@ -1123,11 +994,6 @@ export class SceneEntities {
) )
}, },
onClick: async (args) => { onClick: async (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
// Commit the rectangle to the full AST/code and return to sketch.idle // Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return if (!cornerPoint || args.mouseEvent.button !== 0) return
@ -1239,7 +1105,7 @@ export class SceneEntities {
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
if (addingNewSegmentStatus === 'nothing') { if (addingNewSegmentStatus === 'nothing') {
const prevSegment = sketch.paths[pipeIndex - 2] const prevSegment = sketch.value[pipeIndex - 2]
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -1291,11 +1157,6 @@ export class SceneEntities {
}, },
onMove: () => {}, onMove: () => {},
onClick: (args) => { onClick: (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args?.mouseEvent.which !== 1) return if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) { if (!args || !args.selected) {
sceneInfra.modelingSend({ sceneInfra.modelingSend({
@ -1316,7 +1177,7 @@ export class SceneEntities {
} }
prepareTruncatedMemoryAndAst = ( prepareTruncatedMemoryAndAst = (
sketchPathToNode: PathToNode, sketchPathToNode: PathToNode,
ast?: Node<Program>, ast?: Program,
draftSegment?: DraftSegment draftSegment?: DraftSegment
) => ) =>
prepareTruncatedMemoryAndAst( prepareTruncatedMemoryAndAst(
@ -1337,35 +1198,20 @@ export class SceneEntities {
sketchPathToNode: PathToNode sketchPathToNode: PathToNode
intersects: Intersection<Object3D<Object3DEventMap>>[] intersects: Intersection<Object3D<Object3DEventMap>>[]
draftInfo?: { draftInfo?: {
truncatedAst: Node<Program> truncatedAst: Program
programMemoryOverride: ProgramMemory programMemoryOverride: ProgramMemory
variableDeclarationName: string variableDeclarationName: string
} }
}) { }) {
const intersectsProfileStart = const profileStart =
draftInfo && draftInfo &&
intersects intersects
.map(({ object }) => getParentGroup(object, [PROFILE_START])) .map(({ object }) => getParentGroup(object, [PROFILE_START]))
.find((a) => a?.name === PROFILE_START) .find((a) => a?.name === PROFILE_START)
const intersection2d = intersectsProfileStart const intersection2d = profileStart
? new Vector2( ? new Vector2(profileStart.position.x, profileStart.position.y)
intersectsProfileStart.position.x,
intersectsProfileStart.position.y
)
: _intersection2d : _intersection2d
const intersectsYAxis = intersects.find(
(sceneObject) => sceneObject.object.name === Y_AXIS
)
const intersectsXAxis = intersects.find(
(sceneObject) => sceneObject.object.name === X_AXIS
)
const snappedPoint = new Vector2(
intersectsYAxis ? 0 : intersection2d.x,
intersectsXAxis ? 0 : intersection2d.y
)
const group = getParentGroup(object, SEGMENT_BODIES_PLUS_PROFILE_START) const group = getParentGroup(object, SEGMENT_BODIES_PLUS_PROFILE_START)
const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE]) const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE])
if (!group) return if (!group) return
@ -1385,10 +1231,10 @@ export class SceneEntities {
group.userData.from[0], group.userData.from[0],
group.userData.from[1], group.userData.from[1],
] ]
const dragTo: [number, number] = [snappedPoint.x, snappedPoint.y] const dragTo: [number, number] = [intersection2d.x, intersection2d.y]
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast } let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
const _node = getNodeFromPath<Node<CallExpression>>( const _node = getNodeFromPath<CallExpression>(
modifiedAst, modifiedAst,
pathToNode, pathToNode,
'CallExpression' 'CallExpression'
@ -1400,7 +1246,7 @@ export class SceneEntities {
let modded: let modded:
| { | {
modifiedAst: Node<Program> modifiedAst: Program
pathToNode: PathToNode pathToNode: PathToNode
} }
| Error | Error
@ -1499,7 +1345,7 @@ export class SceneEntities {
} }
if (!sketch) return if (!sketch) return
const sgPaths = sketch.paths const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment( this.updateSegment(
@ -1547,7 +1393,7 @@ export class SceneEntities {
modifiedAst, modifiedAst,
segment.__geoMeta.sourceRange segment.__geoMeta.sourceRange
) )
const sgPaths = sketch.paths const sgPaths = sketch.value
const originalPathToNodeStr = JSON.stringify(segPathToNode) const originalPathToNodeStr = JSON.stringify(segPathToNode)
segPathToNode[1][0] = varDecIndex segPathToNode[1][0] = varDecIndex
const pathToNodeStr = JSON.stringify(segPathToNode) const pathToNodeStr = JSON.stringify(segPathToNode)
@ -1695,7 +1541,7 @@ export class SceneEntities {
if (parent?.userData?.pathToNode) { if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast)) const updatedAst = parse(recast(kclManager.ast))
if (trap(updatedAst)) return if (trap(updatedAst)) return
const _node = getNodeFromPath<Node<CallExpression>>( const _node = getNodeFromPath<CallExpression>(
updatedAst, updatedAst,
parent.userData.pathToNode, parent.userData.pathToNode,
'CallExpression' 'CallExpression'
@ -1830,12 +1676,12 @@ export type DefaultPlaneStr = 'XY' | 'XZ' | 'YZ' | '-XY' | '-XZ' | '-YZ'
function prepareTruncatedMemoryAndAst( function prepareTruncatedMemoryAndAst(
sketchPathToNode: PathToNode, sketchPathToNode: PathToNode,
ast: Node<Program>, ast: Program,
programMemory: ProgramMemory, programMemory: ProgramMemory,
draftSegment?: DraftSegment draftSegment?: DraftSegment
): ):
| { | {
truncatedAst: Node<Program> truncatedAst: Program
programMemoryOverride: ProgramMemory programMemoryOverride: ProgramMemory
variableDeclarationName: string variableDeclarationName: string
} }
@ -1843,7 +1689,7 @@ function prepareTruncatedMemoryAndAst(
const bodyIndex = Number(sketchPathToNode?.[1]?.[0]) || 0 const bodyIndex = Number(sketchPathToNode?.[1]?.[0]) || 0
const _ast = structuredClone(ast) const _ast = structuredClone(ast)
const _node = getNodeFromPath<Node<VariableDeclaration>>( const _node = getNodeFromPath<VariableDeclaration>(
_ast, _ast,
sketchPathToNode || [], sketchPathToNode || [],
'VariableDeclaration' 'VariableDeclaration'
@ -1855,7 +1701,7 @@ function prepareTruncatedMemoryAndAst(
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return sg if (err(sg)) return sg
const lastSeg = sg?.paths.slice(-1)[0] const lastSeg = sg?.value.slice(-1)[0]
if (draftSegment) { if (draftSegment) {
// truncatedAst needs to setup with another segment at the end // truncatedAst needs to setup with another segment at the end
let newSegment let newSegment
@ -1893,15 +1739,15 @@ function prepareTruncatedMemoryAndAst(
).body.slice(-1)[0].start = lastPipeItem.start ).body.slice(-1)[0].start = lastPipeItem.start
_ast.end = lastPipeItem.end _ast.end = lastPipeItem.end
const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration> const varDec = _ast.body[bodyIndex] as VariableDeclaration
varDec.end = lastPipeItem.end varDec.end = lastPipeItem.end
const declarator = varDec.declarations[0] const declarator = varDec.declarations[0]
declarator.end = lastPipeItem.end declarator.end = lastPipeItem.end
const init = declarator.init as Node<PipeExpression> const init = declarator.init as PipeExpression
init.end = lastPipeItem.end init.end = lastPipeItem.end
init.body.slice(-1)[0].end = lastPipeItem.end init.body.slice(-1)[0].end = lastPipeItem.end
} }
const truncatedAst: Node<Program> = { const truncatedAst: Program = {
..._ast, ..._ast,
body: [structuredClone(_ast.body[bodyIndex])], body: [structuredClone(_ast.body[bodyIndex])],
} }

View File

@ -30,7 +30,6 @@ import { MouseState, SegmentOverlayPayload } from 'machines/modelingMachine'
import { getAngle, throttle } from 'lib/utils' import { getAngle, throttle } from 'lib/utils'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer' import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { orthoScale, perspScale } from './helpers'
type SendType = ReturnType<typeof useModelingContext>['send'] type SendType = ReturnType<typeof useModelingContext>['send']
@ -50,10 +49,6 @@ export const RAYCASTABLE_PLANE = 'raycastable-plane'
export const X_AXIS = 'xAxis' export const X_AXIS = 'xAxis'
export const Y_AXIS = 'yAxis' export const Y_AXIS = 'yAxis'
/** the THREEjs representation of the group surrounding a "snapped" point that is not yet placed */
export const DRAFT_POINT_GROUP = 'draft-point-group'
/** the THREEjs representation of a "snapped" point that is not yet placed */
export const DRAFT_POINT = 'draft-point'
export const AXIS_GROUP = 'axisGroup' export const AXIS_GROUP = 'axisGroup'
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments' export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
export const ARROWHEAD = 'arrowhead' export const ARROWHEAD = 'arrowhead'
@ -65,11 +60,6 @@ export interface OnMouseEnterLeaveArgs {
selected: Object3D<Object3DEventMap> selected: Object3D<Object3DEventMap>
dragSelected?: Object3D<Object3DEventMap> dragSelected?: Object3D<Object3DEventMap>
mouseEvent: MouseEvent mouseEvent: MouseEvent
/** The intersection of the mouse with the THREEjs raycast plane */
intersectionPoint?: {
twoD?: Vector2
threeD?: Vector3
}
} }
interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs { interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
@ -223,7 +213,7 @@ export class SceneInfra {
to: Coords2d to: Coords2d
angle?: number angle?: number
}): SegmentOverlayPayload | null { }): SegmentOverlayPayload | null {
if (!group.userData.draft && group.userData.pathToNode && arrowGroup) { if (group.userData.pathToNode && arrowGroup) {
const vector = new Vector3(0, 0, 0) const vector = new Vector3(0, 0, 0)
// Get the position of the object3D in world space // Get the position of the object3D in world space
@ -358,42 +348,29 @@ export class SceneInfra {
window.removeEventListener('resize', this.onWindowResize) window.removeEventListener('resize', this.onWindowResize)
// Dispose of any other resources like geometries, materials, textures // Dispose of any other resources like geometries, materials, textures
} }
getClientSceneScaleFactor(meshOrGroup: Mesh | Group) {
const orthoFactor = orthoScale(this.camControls.camera)
const factor =
(this.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(this.camControls.camera, meshOrGroup)) /
this._baseUnitMultiplier
return factor
}
getPlaneIntersectPoint = (): { getPlaneIntersectPoint = (): {
twoD?: Vector2 twoD?: Vector2
threeD?: Vector3 threeD?: Vector3
intersection: Intersection<Object3D<Object3DEventMap>> intersection: Intersection<Object3D<Object3DEventMap>>
} | null => { } | null => {
// Get the orientations from the camera and mouse position
this.planeRaycaster.setFromCamera( this.planeRaycaster.setFromCamera(
this.currentMouseVector, this.currentMouseVector,
this.camControls.camera this.camControls.camera
) )
// Get the intersection of the ray with the default planes
const planeIntersects = this.planeRaycaster.intersectObjects( const planeIntersects = this.planeRaycaster.intersectObjects(
this.scene.children, this.scene.children,
true true
) )
if (!planeIntersects.length) return null const recastablePlaneIntersect = planeIntersects.find(
// Find the intersection with the raycastable (or sketch) plane
const raycastablePlaneIntersection = planeIntersects.find(
(intersect) => intersect.object.name === RAYCASTABLE_PLANE (intersect) => intersect.object.name === RAYCASTABLE_PLANE
) )
if (!raycastablePlaneIntersection) if (!planeIntersects.length) return null
return { intersection: planeIntersects[0] } if (!recastablePlaneIntersect) return { intersection: planeIntersects[0] }
const planePosition = raycastablePlaneIntersection.object.position const planePosition = planeIntersects[0].object.position
const inversePlaneQuaternion = const inversePlaneQuaternion = planeIntersects[0].object.quaternion
raycastablePlaneIntersection.object.quaternion.clone().invert() .clone()
const intersectPoint = raycastablePlaneIntersection.point .invert()
const intersectPoint = planeIntersects[0].point
let transformedPoint = intersectPoint.clone() let transformedPoint = intersectPoint.clone()
if (transformedPoint) { if (transformedPoint) {
transformedPoint.applyQuaternion(inversePlaneQuaternion) transformedPoint.applyQuaternion(inversePlaneQuaternion)
@ -470,26 +447,18 @@ export class SceneInfra {
if (intersects[0]) { if (intersects[0]) {
const firstIntersectObject = intersects[0].object const firstIntersectObject = intersects[0].object
const planeIntersectPoint = this.getPlaneIntersectPoint()
const intersectionPoint = {
twoD: planeIntersectPoint?.twoD,
threeD: planeIntersectPoint?.threeD,
}
if (this.hoveredObject !== firstIntersectObject) { if (this.hoveredObject !== firstIntersectObject) {
const hoveredObj = this.hoveredObject const hoveredObj = this.hoveredObject
this.hoveredObject = null this.hoveredObject = null
await this.onMouseLeave({ await this.onMouseLeave({
selected: hoveredObj, selected: hoveredObj,
mouseEvent: mouseEvent, mouseEvent: mouseEvent,
intersectionPoint,
}) })
this.hoveredObject = firstIntersectObject this.hoveredObject = firstIntersectObject
await this.onMouseEnter({ await this.onMouseEnter({
selected: this.hoveredObject, selected: this.hoveredObject,
dragSelected: this.selected?.object, dragSelected: this.selected?.object,
mouseEvent: mouseEvent, mouseEvent: mouseEvent,
intersectionPoint,
}) })
if (!this.selected) if (!this.selected)
this.updateMouseState({ this.updateMouseState({

View File

@ -45,7 +45,6 @@ import {
import { getTangentPointFromPreviousArc } from 'lib/utils2d' import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { import {
ARROWHEAD, ARROWHEAD,
DRAFT_POINT,
SceneInfra, SceneInfra,
SEGMENT_LENGTH_LABEL, SEGMENT_LENGTH_LABEL,
SEGMENT_LENGTH_LABEL_OFFSET_PX, SEGMENT_LENGTH_LABEL_OFFSET_PX,
@ -59,7 +58,7 @@ import { err } from 'lib/trap'
interface CreateSegmentArgs { interface CreateSegmentArgs {
input: SegmentInputs input: SegmentInputs
prevSegment: Sketch['paths'][number] prevSegment: Sketch['value'][number]
id: string id: string
pathToNode: PathToNode pathToNode: PathToNode
isDraftSegment?: boolean isDraftSegment?: boolean
@ -73,7 +72,7 @@ interface CreateSegmentArgs {
interface UpdateSegmentArgs { interface UpdateSegmentArgs {
input: SegmentInputs input: SegmentInputs
prevSegment: Sketch['paths'][number] prevSegment: Sketch['value'][number]
group: Group group: Group
sceneInfra: SceneInfra sceneInfra: SceneInfra
scale?: number scale?: number
@ -148,7 +147,6 @@ class StraightSegment implements SegmentUtils {
segmentGroup.name = STRAIGHT_SEGMENT segmentGroup.name = STRAIGHT_SEGMENT
segmentGroup.userData = { segmentGroup.userData = {
type: STRAIGHT_SEGMENT, type: STRAIGHT_SEGMENT,
draft: isDraftSegment,
id, id,
from, from,
to, to,
@ -349,7 +347,6 @@ class TangentialArcToSegment implements SegmentUtils {
mesh.name = meshName mesh.name = meshName
group.userData = { group.userData = {
type: TANGENTIAL_ARC_TO_SEGMENT, type: TANGENTIAL_ARC_TO_SEGMENT,
draft: isDraftSegment,
id, id,
from, from,
to, to,
@ -518,18 +515,11 @@ class CircleSegment implements SegmentUtils {
const meshType = isDraftSegment ? CIRCLE_SEGMENT_DASH : CIRCLE_SEGMENT_BODY const meshType = isDraftSegment ? CIRCLE_SEGMENT_DASH : CIRCLE_SEGMENT_BODY
const arrowGroup = createArrowhead(scale, theme, color) const arrowGroup = createArrowhead(scale, theme, color)
const circleCenterGroup = createCircleCenterHandle(scale, theme, color) const circleCenterGroup = createCircleCenterHandle(scale, theme, color)
// A radius indicator that appears from the center to the perimeter
const radiusIndicatorGroup = createLengthIndicator({
from: center,
to: [center[0] + radius, center[1]],
scale,
})
arcMesh.userData.type = meshType arcMesh.userData.type = meshType
arcMesh.name = meshType arcMesh.name = meshType
group.userData = { group.userData = {
type: CIRCLE_SEGMENT, type: CIRCLE_SEGMENT,
draft: isDraftSegment,
id, id,
from, from,
radius, radius,
@ -542,7 +532,7 @@ class CircleSegment implements SegmentUtils {
} }
group.name = CIRCLE_SEGMENT group.name = CIRCLE_SEGMENT
group.add(arcMesh, arrowGroup, circleCenterGroup, radiusIndicatorGroup) group.add(arcMesh, arrowGroup, circleCenterGroup)
const updateOverlaysCallback = this.update({ const updateOverlaysCallback = this.update({
prevSegment, prevSegment,
input, input,
@ -574,9 +564,6 @@ class CircleSegment implements SegmentUtils {
group.userData.radius = radius group.userData.radius = radius
group.userData.prevSegment = prevSegment group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const radiusLengthIndicator = group.getObjectByName(
SEGMENT_LENGTH_LABEL
) as Group
const circleCenterHandle = group.getObjectByName( const circleCenterHandle = group.getObjectByName(
CIRCLE_CENTER_HANDLE CIRCLE_CENTER_HANDLE
) as Group ) as Group
@ -594,14 +581,11 @@ class CircleSegment implements SegmentUtils {
} }
if (arrowGroup) { if (arrowGroup) {
// The arrowhead is placed at the perimeter of the circle, arrowGroup.position.set(
// pointing up and to the right center[0] + Math.cos(Math.PI / 4) * radius,
const arrowPoint = { center[1] + Math.sin(Math.PI / 4) * radius,
x: center[0] + Math.cos(Math.PI / 4) * radius, 0
y: center[1] + Math.sin(Math.PI / 4) * radius, )
}
arrowGroup.position.set(arrowPoint.x, arrowPoint.y, 0)
const arrowheadAngle = Math.PI / 4 const arrowheadAngle = Math.PI / 4
arrowGroup.quaternion.setFromUnitVectors( arrowGroup.quaternion.setFromUnitVectors(
@ -612,31 +596,6 @@ class CircleSegment implements SegmentUtils {
arrowGroup.visible = isHandlesVisible arrowGroup.visible = isHandlesVisible
} }
if (radiusLengthIndicator) {
// The radius indicator is placed at the midpoint of the radius,
// at a 45 degree CCW angle from the positive X-axis
const indicatorPoint = {
x: center[0] + (Math.cos(Math.PI / 4) * radius) / 2,
y: center[1] + (Math.sin(Math.PI / 4) * radius) / 2,
}
const labelWrapper = radiusLengthIndicator.getObjectByName(
SEGMENT_LENGTH_LABEL_TEXT
) as CSS2DObject
const labelWrapperElem = labelWrapper.element as HTMLDivElement
const label = labelWrapperElem.children[0] as HTMLParagraphElement
label.innerText = `${roundOff(radius)}`
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
const isPlaneBackFace = center[0] > indicatorPoint.x
label.style.setProperty(
'--degree',
`${isPlaneBackFace ? '45' : '-45'}deg`
)
label.style.setProperty('--x', `0px`)
label.style.setProperty('--y', `0px`)
labelWrapper.position.set(indicatorPoint.x, indicatorPoint.y, 0)
radiusLengthIndicator.visible = isHandlesVisible
}
if (circleCenterHandle) { if (circleCenterHandle) {
circleCenterHandle.position.set(center[0], center[1], 0) circleCenterHandle.position.set(center[0], center[1], 0)
circleCenterHandle.scale.set(scale, scale, scale) circleCenterHandle.scale.set(scale, scale, scale)
@ -687,20 +646,19 @@ class CircleSegment implements SegmentUtils {
export function createProfileStartHandle({ export function createProfileStartHandle({
from, from,
isDraft = false, id,
pathToNode,
scale = 1, scale = 1,
theme, theme,
isSelected, isSelected,
...rest
}: { }: {
from: Coords2d from: Coords2d
id: string
pathToNode: PathToNode
scale?: number scale?: number
theme: Themes theme: Themes
isSelected?: boolean isSelected?: boolean
} & ( }) {
| { isDraft: true }
| { isDraft: false; id: string; pathToNode: PathToNode }
)) {
const group = new Group() const group = new Group()
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
@ -713,12 +671,13 @@ export function createProfileStartHandle({
group.userData = { group.userData = {
type: PROFILE_START, type: PROFILE_START,
id,
from, from,
pathToNode,
isSelected, isSelected,
baseColor, baseColor,
...rest,
} }
group.name = isDraft ? DRAFT_POINT : PROFILE_START group.name = PROFILE_START
group.position.set(from[0], from[1], 0) group.position.set(from[0], from[1], 0)
group.scale.set(scale, scale, scale) group.scale.set(scale, scale, scale)
return group return group

View File

@ -138,13 +138,6 @@ const FileTreeItem = ({
// the ReactNodes are destroyed, so is this listener :) // the ReactNodes are destroyed, so is this listener :)
useFileSystemWatcher( useFileSystemWatcher(
async (eventType, path) => { async (eventType, path) => {
// Prevents a cyclic read / write causing editor problems such as
// misplaced cursor positions.
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
return
}
// Don't try to read a file that was removed. // Don't try to read a file that was removed.
if (isCurrentFile && eventType !== 'unlink') { if (isCurrentFile && eventType !== 'unlink') {
let code = await window.electron.readFile(path, { encoding: 'utf-8' }) let code = await window.electron.readFile(path, { encoding: 'utf-8' })
@ -194,11 +187,11 @@ const FileTreeItem = ({
// Show the renaming form // Show the renaming form
addCurrentItemToRenaming() addCurrentItemToRenaming()
} else if (e.code === 'Space') { } else if (e.code === 'Space') {
void handleClick() handleClick()
} }
} }
async function handleClick() { function handleClick() {
if (fileOrDir.children !== null) return // Don't open directories if (fileOrDir.children !== null) return // Don't open directories
if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) { if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) {
@ -208,10 +201,12 @@ const FileTreeItem = ({
`import("${fileOrDir.path.replace(project.path, '.')}")\n` + `import("${fileOrDir.path.replace(project.path, '.')}")\n` +
codeManager.code codeManager.code
) )
await codeManager.writeToFile() // eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.writeToFile()
// Prevent seeing the model built one piece at a time when changing files // Prevent seeing the model built one piece at a time when changing files
await kclManager.executeCode(true) // eslint-disable-next-line @typescript-eslint/no-floating-promises
kclManager.executeCode(true)
} else { } else {
// Let the lsp servers know we closed a file. // Let the lsp servers know we closed a file.
onFileClose(currentFile?.path || null, project?.path || null) onFileClose(currentFile?.path || null, project?.path || null)
@ -240,7 +235,7 @@ const FileTreeItem = ({
style={{ paddingInlineStart: getIndentationCSS(level) }} style={{ paddingInlineStart: getIndentationCSS(level) }}
onClick={(e) => { onClick={(e) => {
e.currentTarget.focus() e.currentTarget.focus()
void handleClick() handleClick()
}} }}
onKeyUp={handleKeyUp} onKeyUp={handleKeyUp}
> >
@ -499,13 +494,6 @@ export const FileTreeInner = ({
const isCurrentFile = loaderData.file?.path === path const isCurrentFile = loaderData.file?.path === path
const hasChanged = eventType === 'change' const hasChanged = eventType === 'change'
if (isCurrentFile && hasChanged) return if (isCurrentFile && hasChanged) return
// If it's a settings file we wrote to already from the app ignore it.
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
return
}
fileSend({ type: 'Refresh' }) fileSend({ type: 'Refresh' })
}, },
[loaderData?.project?.path, fileContext.selectedDirectory.path].filter( [loaderData?.project?.path, fileContext.selectedDirectory.path].filter(
@ -543,19 +531,3 @@ export const FileTreeInner = ({
</div> </div>
) )
} }
export const FileTreeRoot = () => {
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const { project } = loaderData
// project.path should never be empty here but I guess during initial loading
// it can be.
return (
<div
className="max-w-xs text-ellipsis overflow-hidden cursor-pointer"
title={project?.path ?? ''}
>
{project?.name ?? ''}
</div>
)
}

View File

@ -4,7 +4,7 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { createAndOpenNewTutorialProject } from 'lib/desktopFS' import { createAndOpenNewProject } from 'lib/desktopFS'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { useLspContext } from './LspProvider' import { useLspContext } from './LspProvider'
import { openExternalBrowserIfDesktop } from 'lib/openWindow' import { openExternalBrowserIfDesktop } from 'lib/openWindow'
@ -116,10 +116,9 @@ export function HelpMenu(props: React.PropsWithChildren) {
if (isInProject) { if (isInProject) {
navigate(filePath + PATHS.ONBOARDING.INDEX) navigate(filePath + PATHS.ONBOARDING.INDEX)
} else { } else {
createAndOpenNewTutorialProject({ createAndOpenNewProject({ onProjectOpen, navigate }).catch(
onProjectOpen, reportRejection
navigate, )
}).catch(reportRejection)
} }
}} }}
> >

View File

@ -23,7 +23,6 @@ export function LowerRightControls({
}) { }) {
const location = useLocation() const location = useLocation()
const filePath = useAbsoluteFilePath() const filePath = useAbsoluteFilePath()
const linkOverrideClassName = const linkOverrideClassName =
'!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30' '!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30'

View File

@ -1,123 +0,0 @@
import { createContext, useEffect, useState } from 'react'
import { engineCommandManager } from 'lib/singletons'
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
import { isDesktop } from 'lib/isDesktop'
import { components } from 'lib/machine-api'
import { reportRejection } from 'lib/trap'
import { toSync } from 'lib/utils'
export type MachinesListing = Array<
components['schemas']['MachineInfoResponse']
>
export interface MachineManager {
machines: MachinesListing
machineApiIp: string | null
currentMachine: components['schemas']['MachineInfoResponse'] | null
noMachinesReason: () => string | undefined
setCurrentMachine: (
m: components['schemas']['MachineInfoResponse'] | null
) => void
}
export const MachineManagerContext = createContext<MachineManager>({
machines: [],
machineApiIp: null,
currentMachine: null,
setCurrentMachine: (
_: components['schemas']['MachineInfoResponse'] | null
) => {},
noMachinesReason: () => undefined,
})
export const MachineManagerProvider = ({
children,
}: {
children: React.ReactNode
}) => {
const [machines, setMachines] = useState<MachinesListing>([])
const [machineApiIp, setMachineApiIp] = useState<string | null>(null)
const [currentMachine, setCurrentMachine] = useState<
components['schemas']['MachineInfoResponse'] | null
>(null)
const commandBarActor = CommandsContext.useActorRef()
// Get the reason message for why there are no machines.
const noMachinesReason = (): string | undefined => {
if (machines.length > 0) {
return undefined
}
if (machineApiIp === null) {
return 'Machine API server was not discovered'
}
return 'Machine API server was discovered, but no machines are available'
}
useEffect(() => {
if (!isDesktop()) return
const update = async () => {
const _machineApiIp = await window.electron.getMachineApiIp()
if (_machineApiIp === null) return
setMachineApiIp(_machineApiIp)
const _machines = await window.electron.listMachines(_machineApiIp)
setMachines(_machines)
}
// Start a background job to update the machines every ten seconds.
// If MDNS is already watching, this timeout will wait until it's done to trigger the
// finding again.
let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined
const timeoutLoop = () => {
clearTimeout(timeoutId)
timeoutId = setTimeout(
toSync(async () => {
await update()
timeoutLoop()
}, reportRejection),
1000
)
}
timeoutLoop()
update().catch(reportRejection)
}, [])
// Update engineCommandManager's copy of this data.
useEffect(() => {
const machineManagerNext = {
machines,
machineApiIp,
currentMachine,
noMachinesReason,
setCurrentMachine,
}
engineCommandManager.machineManager = machineManagerNext
commandBarActor.send({
type: 'Set machine manager',
data: machineManagerNext,
})
}, [machines, machineApiIp, currentMachine])
return (
<MachineManagerContext.Provider
value={{
machines,
machineApiIp,
currentMachine,
setCurrentMachine,
noMachinesReason,
}}
>
{' '}
{children}{' '}
</MachineManagerContext.Provider>
)
}

View File

@ -1,11 +1,5 @@
import { useMachine } from '@xstate/react' import { useMachine } from '@xstate/react'
import React, { import React, { createContext, useEffect, useMemo, useRef } from 'react'
createContext,
useEffect,
useMemo,
useRef,
useContext,
} from 'react'
import { import {
Actor, Actor,
AnyStateMachine, AnyStateMachine,
@ -34,7 +28,7 @@ import {
editorManager, editorManager,
sceneEntitiesManager, sceneEntitiesManager,
} from 'lib/singletons' } from 'lib/singletons'
import { MachineManagerContext } from 'components/MachineManagerProvider' import { machineManager } from 'lib/machineManager'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance' import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
import { import {
@ -91,7 +85,6 @@ import { submitAndAwaitTextToKcl } from 'lib/textToCad'
import { useFileContext } from 'hooks/useFileContext' import { useFileContext } from 'hooks/useFileContext'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
import { IndexLoaderData } from 'lib/types' import { IndexLoaderData } from 'lib/types'
import { Node } from 'wasm-lib/kcl/bindings/Node'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -147,8 +140,6 @@ export const ModelingMachineProvider = ({
// > // >
// ) // )
const machineManager = useContext(MachineManagerContext)
const [modelingState, modelingSend, modelingActor] = useMachine( const [modelingState, modelingSend, modelingActor] = useMachine(
modelingMachine.provide({ modelingMachine.provide({
actions: { actions: {
@ -417,7 +408,7 @@ export const ModelingMachineProvider = ({
return {} return {}
} }
), ),
Make: ({ context, event }) => { Make: ({ event }) => {
if (event.type !== 'Make') return if (event.type !== 'Make') return
// Check if we already have an export intent. // Check if we already have an export intent.
if (engineCommandManager.exportInfo) { if (engineCommandManager.exportInfo) {
@ -431,21 +422,7 @@ export const ModelingMachineProvider = ({
} }
// Set the current machine. // Set the current machine.
// Due to our use of singeton pattern, we need to do this to reliably machineManager.currentMachine = event.data.machine
// update this object across React and non-React boundary.
// We need to do this eagerly because of the exportToEngine call below.
if (engineCommandManager.machineManager === null) {
console.warn(
"engineCommandManager.machineManager is null. It shouldn't be at this point. Aborting operation."
)
return
} else {
engineCommandManager.machineManager.currentMachine =
event.data.machine
}
// Update the rest of the UI that needs to know the current machine
context.machineManager.setCurrentMachine(event.data.machine)
const format: Models['OutputFormat_type'] = { const format: Models['OutputFormat_type'] = {
type: 'stl', type: 'stl',
@ -667,7 +644,6 @@ export const ModelingMachineProvider = ({
input.plane input.plane
) )
await kclManager.updateAst(modifiedAst, false) await kclManager.updateAst(modifiedAst, false)
sceneInfra.camControls.enableRotate = false
sceneInfra.camControls.syncDirection = 'clientToEngine' sceneInfra.camControls.syncDirection = 'clientToEngine'
await letEngineAnimateAndSyncCamAfter( await letEngineAnimateAndSyncCamAfter(
@ -972,7 +948,7 @@ export const ModelingMachineProvider = ({
}) })
let parsed = parse(recast(kclManager.ast)) let parsed = parse(recast(kclManager.ast))
if (trap(parsed)) return Promise.reject(parsed) if (trap(parsed)) return Promise.reject(parsed)
parsed = parsed as Node<Program> parsed = parsed as Program
const { modifiedAst: _modifiedAst, pathToReplacedNode } = const { modifiedAst: _modifiedAst, pathToReplacedNode } =
moveValueIntoNewVariablePath( moveValueIntoNewVariablePath(
@ -983,7 +959,7 @@ export const ModelingMachineProvider = ({
) )
parsed = parse(recast(_modifiedAst)) parsed = parse(recast(_modifiedAst))
if (trap(parsed)) return Promise.reject(parsed) if (trap(parsed)) return Promise.reject(parsed)
parsed = parsed as Node<Program> parsed = parsed as Program
if (!pathToReplacedNode) if (!pathToReplacedNode)
return Promise.reject(new Error('No path to replaced node')) return Promise.reject(new Error('No path to replaced node'))
@ -1018,7 +994,6 @@ export const ModelingMachineProvider = ({
...modelingMachineDefaultContext.store, ...modelingMachineDefaultContext.store,
...persistedContext, ...persistedContext,
}, },
machineManager,
}, },
// devTools: true, // devTools: true,
} }

View File

@ -1,4 +1,3 @@
import { ReactNode } from 'react'
import styles from './ModelingPane.module.css' import styles from './ModelingPane.module.css'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
@ -7,24 +6,22 @@ import { CustomIconName } from 'components/CustomIcon'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons' import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from 'components/ActionIcon' import { ActionIcon } from 'components/ActionIcon'
export interface ModelingPaneProps { export interface ModelingPaneProps
id: string extends React.PropsWithChildren,
children: ReactNode | ReactNode[] React.HTMLAttributes<HTMLDivElement> {
className?: string
icon?: CustomIconName | IconDefinition icon?: CustomIconName | IconDefinition
title: ReactNode title: string
Menu?: React.ReactNode | React.FC Menu?: React.ReactNode | React.FC
detailsTestId?: string detailsTestId?: string
onClose: () => void onClose: () => void
} }
export const ModelingPaneHeader = ({ export const ModelingPaneHeader = ({
id,
icon, icon,
title, title,
Menu, Menu,
onClose, onClose,
}: Pick<ModelingPaneProps, 'id' | 'icon' | 'title' | 'Menu' | 'onClose'>) => { }: Pick<ModelingPaneProps, 'icon' | 'title' | 'Menu' | 'onClose'>) => {
return ( return (
<div className={styles.header}> <div className={styles.header}>
<div className="flex gap-2 items-center flex-1"> <div className="flex gap-2 items-center flex-1">
@ -37,7 +34,7 @@ export const ModelingPaneHeader = ({
bgClassName="!bg-transparent" bgClassName="!bg-transparent"
/> />
)} )}
<span data-testid={id + '-header'}>{title}</span> <span>{title}</span>
</div> </div>
{Menu instanceof Function ? <Menu /> : Menu} {Menu instanceof Function ? <Menu /> : Menu}
<ActionButton <ActionButton
@ -89,7 +86,6 @@ export const ModelingPane = ({
} }
> >
<ModelingPaneHeader <ModelingPaneHeader
id={id}
icon={icon} icon={icon}
title={title} title={title}
Menu={Menu} Menu={Menu}

View File

@ -88,30 +88,25 @@ export const MemoryPane = () => {
export const processMemory = (programMemory: ProgramMemory) => { export const processMemory = (programMemory: ProgramMemory) => {
const processedMemory: any = {} const processedMemory: any = {}
for (const [key, val] of programMemory?.visibleEntries()) { for (const [key, val] of programMemory?.visibleEntries()) {
if ( if (typeof val.value !== 'function') {
(val.type === 'UserVal' && val.value.type === 'Sketch') || const sg = sketchFromKclValue(val, null)
// @ts-ignore
(val.type !== 'Function' && val.type !== 'UserVal')
) {
const sg = sketchFromKclValue(val, key)
if (val.type === 'Solid') { if (val.type === 'Solid') {
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => { processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
return rest return rest
}) })
} else if (!err(sg)) { } else if (!err(sg)) {
processedMemory[key] = sg.paths.map(({ __geoMeta, ...rest }: Path) => { processedMemory[key] = sg.value.map(({ __geoMeta, ...rest }: Path) => {
return rest return rest
}) })
} else if ((val.type as any) === 'Function') {
processedMemory[key] = `__function(${(val as any)?.expression?.params
?.map?.(({ identifier }: any) => identifier?.name || '')
.join(', ')})__`
} else { } else {
processedMemory[key] = val.value processedMemory[key] = val.value
} }
//@ts-ignore } else if (key !== 'log') {
} else if (val.type === 'Function') { processedMemory[key] = '__function__'
processedMemory[key] = `__function(${(val as any)?.expression?.params
?.map?.(({ identifier }: any) => identifier?.name || '')
.join(', ')})__`
} else {
processedMemory[key] = val.value
} }
} }
return processedMemory return processedMemory

View File

@ -6,7 +6,7 @@ import { MouseEventHandler, ReactNode } from 'react'
import { MemoryPane, MemoryPaneMenu } from './MemoryPane' import { MemoryPane, MemoryPaneMenu } from './MemoryPane'
import { LogsPane } from './LoggingPanes' import { LogsPane } from './LoggingPanes'
import { DebugPane } from './DebugPane' import { DebugPane } from './DebugPane'
import { FileTreeInner, FileTreeMenu, FileTreeRoot } from 'components/FileTree' import { FileTreeInner, FileTreeMenu } from 'components/FileTree'
import { useKclContext } from 'lang/KclProvider' import { useKclContext } from 'lang/KclProvider'
import { editorManager } from 'lib/singletons' import { editorManager } from 'lib/singletons'
import { ContextFrom } from 'xstate' import { ContextFrom } from 'xstate'
@ -38,8 +38,7 @@ interface PaneCallbackProps {
export type SidebarPane = { export type SidebarPane = {
id: SidebarType id: SidebarType
title: ReactNode title: string
sidebarName?: string
icon: CustomIconName | IconDefinition icon: CustomIconName | IconDefinition
keybinding: string keybinding: string
Content: ReactNode | React.FC Content: ReactNode | React.FC
@ -50,7 +49,7 @@ export type SidebarPane = {
export type SidebarAction = { export type SidebarAction = {
id: string id: string
title: ReactNode title: string
icon: CustomIconName icon: CustomIconName
iconClassName?: string // Just until we get rid of FontAwesome icons iconClassName?: string // Just until we get rid of FontAwesome icons
keybinding: string keybinding: string
@ -79,8 +78,7 @@ export const sidebarPanes: SidebarPane[] = [
}, },
{ {
id: 'files', id: 'files',
title: <FileTreeRoot />, title: 'Project Files',
sidebarName: 'Project Files',
icon: 'folder', icon: 'folder',
Content: FileTreeInner, Content: FileTreeInner,
keybinding: 'Shift + F', keybinding: 'Shift + F',

View File

@ -1,13 +1,6 @@
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Resizable } from 're-resizable' import { Resizable } from 're-resizable'
import { import { MouseEventHandler, useCallback, useEffect, useMemo } from 'react'
MouseEventHandler,
useCallback,
useEffect,
useMemo,
ReactNode,
useContext,
} from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { SidebarAction, SidebarType, sidebarPanes } from './ModelingPanes' import { SidebarAction, SidebarType, sidebarPanes } from './ModelingPanes'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
@ -20,7 +13,7 @@ import { CustomIconName } from 'components/CustomIcon'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons' import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { useKclContext } from 'lang/KclProvider' import { useKclContext } from 'lang/KclProvider'
import { MachineManagerContext } from 'components/MachineManagerProvider' import { machineManager } from 'lib/machineManager'
interface ModelingSidebarProps { interface ModelingSidebarProps {
paneOpacity: '' | 'opacity-20' | 'opacity-40' paneOpacity: '' | 'opacity-20' | 'opacity-40'
@ -36,7 +29,6 @@ function getPlatformString(): 'web' | 'desktop' {
} }
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) { export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
const machineManager = useContext(MachineManagerContext)
const { commandBarSend } = useCommandsContext() const { commandBarSend } = useCommandsContext()
const kclContext = useKclContext() const kclContext = useKclContext()
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
@ -271,8 +263,7 @@ interface ModelingPaneButtonProps
extends React.HTMLAttributes<HTMLButtonElement> { extends React.HTMLAttributes<HTMLButtonElement> {
paneConfig: { paneConfig: {
id: string id: string
title: ReactNode title: string
sidebarName?: string
icon: CustomIconName | IconDefinition icon: CustomIconName | IconDefinition
keybinding: string keybinding: string
iconClassName?: string iconClassName?: string
@ -301,10 +292,7 @@ function ModelingPaneButton({
<button <button
className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary" className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
onClick={onClick} onClick={onClick}
name={ name={paneConfig.title}
paneConfig.sidebarName ??
(typeof paneConfig.title === 'string' ? paneConfig.title : '')
}
data-testid={paneConfig.id + '-pane-button'} data-testid={paneConfig.id + '-pane-button'}
disabled={disabledText !== undefined} disabled={disabledText !== undefined}
aria-disabled={disabledText !== undefined} aria-disabled={disabledText !== undefined}
@ -320,7 +308,7 @@ function ModelingPaneButton({
} }
/> />
<span className="sr-only"> <span className="sr-only">
{paneConfig.sidebarName ?? paneConfig.title} {paneConfig.title}
{paneIsOpen !== undefined ? ` pane` : ''} {paneIsOpen !== undefined ? ` pane` : ''}
</span> </span>
<Tooltip <Tooltip
@ -329,7 +317,7 @@ function ModelingPaneButton({
hoverOnly hoverOnly
> >
<span className="flex-1"> <span className="flex-1">
{paneConfig.sidebarName ?? paneConfig.title} {paneConfig.title}
{disabledText !== undefined ? ` (${disabledText})` : ''} {disabledText !== undefined ? ` (${disabledText})` : ''}
{paneIsOpen !== undefined ? ` pane` : ''} {paneIsOpen !== undefined ? ` pane` : ''}
</span> </span>

View File

@ -1,9 +1,7 @@
import { Popover } from '@headlessui/react' import { Popover } from '@headlessui/react'
import { useContext } from 'react'
import Tooltip from './Tooltip' import Tooltip from './Tooltip'
import { machineManager } from 'lib/machineManager'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { components } from 'lib/machine-api'
import { MachineManagerContext } from 'components/MachineManagerProvider'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
export const NetworkMachineIndicator = ({ export const NetworkMachineIndicator = ({
@ -11,12 +9,9 @@ export const NetworkMachineIndicator = ({
}: { }: {
className?: string className?: string
}) => { }) => {
const { const machineCount = machineManager.machineCount()
noMachinesReason, const reason = machineManager.noMachinesReason()
machines, const machines = machineManager.machines
machines: { length: machineCount },
} = useContext(MachineManagerContext)
const reason = noMachinesReason()
return isDesktop() ? ( return isDesktop() ? (
<Popover className="relative"> <Popover className="relative">
@ -52,36 +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">
{machines.map( {machines.map((machine) => {
(machine: components['schemas']['MachineInfoResponse']) => { return (
return ( <li key={machine.id} 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="">{machine.id.toUpperCase()}</p>
<p className="">{machine.id.toUpperCase()}</p> <p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs"> {machine.make_model.model}
{machine.make_model.model} </p>
</p> {machine.extra &&
{machine.extra && machine.extra.type === 'bambu' &&
machine.extra.type === 'bambu' && machine.extra.nozzle_diameter && (
machine.extra.nozzle_diameter && ( <p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs"> Nozzle Diameter: {machine.extra.nozzle_diameter}
Nozzle Diameter: {machine.extra.nozzle_diameter} </p>
</p> )}
)} <p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs"> {`Status: ${machine.state.state
{`Status: ${machine.state.state .charAt(0)
.charAt(0) .toUpperCase()}${machine.state.state.slice(1)}`}
.toUpperCase()}${machine.state.state.slice(1)}`} {machine.state.state === 'failed' && machine.state.message
{machine.state.state === 'failed' && machine.state.message ? ` (${machine.state.message})`
? ` (${machine.state.message})` : ''}
: ''} {machine.state.state === 'running' && machine.progress
{machine.state.state === 'running' && machine.progress ? ` (${Math.round(machine.progress)}%)`
? ` (${Math.round(machine.progress)}%)` : ''}
: ''} </p>
</p> </li>
</li> )
) })}
}
)}
</ul> </ul>
)} )}
</Popover.Panel> </Popover.Panel>

View File

@ -4,14 +4,14 @@ import { type IndexLoaderData } from 'lib/types'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { isDesktop } from '../lib/isDesktop' import { isDesktop } from '../lib/isDesktop'
import { Link, useLocation, useNavigate } from 'react-router-dom' import { Link, useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useMemo, useContext } from 'react' import { Fragment, useMemo } from 'react'
import { Logo } from './Logo' import { Logo } from './Logo'
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { useLspContext } from './LspProvider' import { useLspContext } from './LspProvider'
import { engineCommandManager } from 'lib/singletons' import { engineCommandManager } from 'lib/singletons'
import { MachineManagerContext } from 'components/MachineManagerProvider' import { machineManager } from 'lib/machineManager'
import usePlatform from 'hooks/usePlatform' import usePlatform from 'hooks/usePlatform'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import Tooltip from './Tooltip' import Tooltip from './Tooltip'
@ -96,8 +96,6 @@ function ProjectMenuPopover({
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const filePath = useAbsoluteFilePath() const filePath = useAbsoluteFilePath()
const machineManager = useContext(MachineManagerContext)
const { commandBarState, commandBarSend } = useCommandsContext() const { commandBarState, commandBarSend } = useCommandsContext()
const { onProjectClose } = useLspContext() const { onProjectClose } = useLspContext()
const exportCommandInfo = { name: 'Export', groupId: 'modeling' } const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
@ -108,7 +106,7 @@ function ProjectMenuPopover({
(c) => c.name === obj.name && c.groupId === obj.groupId (c) => c.name === obj.name && c.groupId === obj.groupId
) )
) )
const machineCount = machineManager.machines.length const machineCount = machineManager.machineCount()
// We filter this memoized list so that no orphan "break" elements are rendered. // We filter this memoized list so that no orphan "break" elements are rendered.
const projectMenuItems = useMemo<(ActionButtonProps | 'break')[]>( const projectMenuItems = useMemo<(ActionButtonProps | 'break')[]>(

View File

@ -1,289 +0,0 @@
import { useMachine } from '@xstate/react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { useProjectsLoader } from 'hooks/useProjectsLoader'
import { projectsMachine } from 'machines/projectsMachine'
import { createContext, useEffect, useState } from 'react'
import { Actor, AnyStateMachine, fromPromise, Prop, StateFrom } from 'xstate'
import { useLspContext } from './LspProvider'
import toast from 'react-hot-toast'
import { useLocation, useNavigate } from 'react-router-dom'
import { PATHS } from 'lib/paths'
import {
createNewProjectDirectory,
listProjects,
renameProjectDirectory,
} from 'lib/desktop'
import {
getNextProjectIndex,
interpolateProjectNameWithIndex,
doesProjectNameNeedInterpolated,
} from 'lib/desktopFS'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import useStateMachineCommands from 'hooks/useStateMachineCommands'
import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig'
import { isDesktop } from 'lib/isDesktop'
type MachineContext<T extends AnyStateMachine> = {
state?: StateFrom<T>
send: Prop<Actor<T>, 'send'>
}
export const ProjectsMachineContext = createContext(
{} as MachineContext<typeof projectsMachine>
)
/**
* Watches the project directory and provides project management-related commands,
* like "Create project", "Open project", "Delete project", etc.
*
* If in the future we implement full-fledge project management in the web version,
* we can unify these components but for now, we need this to be only for the desktop version.
*/
export const ProjectsContextProvider = ({
children,
}: {
children: React.ReactNode
}) => {
return isDesktop() ? (
<ProjectsContextDesktop>{children}</ProjectsContextDesktop>
) : (
<ProjectsContextWeb>{children}</ProjectsContextWeb>
)
}
const ProjectsContextWeb = ({ children }: { children: React.ReactNode }) => {
return (
<ProjectsMachineContext.Provider
value={{
state: undefined,
send: () => {},
}}
>
{children}
</ProjectsMachineContext.Provider>
)
}
const ProjectsContextDesktop = ({
children,
}: {
children: React.ReactNode
}) => {
const navigate = useNavigate()
const location = useLocation()
const { commandBarSend } = useCommandsContext()
const { onProjectOpen } = useLspContext()
const {
settings: { context: settings },
} = useSettingsAuthContext()
useEffect(() => {
console.log(
'project directory changed',
settings.app.projectDirectory.current
)
}, [settings.app.projectDirectory.current])
const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)
const { projectPaths, projectsDir } = useProjectsLoader([
projectsLoaderTrigger,
])
// Re-read projects listing if the projectDir has any updates.
useFileSystemWatcher(
async () => {
return setProjectsLoaderTrigger(projectsLoaderTrigger + 1)
},
projectsDir ? [projectsDir] : []
)
const [state, send, actor] = useMachine(
projectsMachine.provide({
actions: {
navigateToProject: ({ context, event }) => {
const nameFromEventData =
'data' in event &&
event.data &&
'name' in event.data &&
event.data.name
const nameFromOutputData =
'output' in event &&
event.output &&
'name' in event.output &&
event.output.name
const name = nameFromEventData || nameFromOutputData
if (name) {
let projectPath =
context.defaultDirectory + window.electron.path.sep + name
onProjectOpen(
{
name,
path: projectPath,
},
null
)
commandBarSend({ type: 'Close' })
const newPathName = `${PATHS.FILE}/${encodeURIComponent(
projectPath
)}`
navigate(newPathName)
}
},
navigateToProjectIfNeeded: ({ event }) => {
if (
event.type.startsWith('xstate.done.actor.') &&
'output' in event
) {
const isInAProject = location.pathname.startsWith(PATHS.FILE)
const isInDeletedProject =
event.type === 'xstate.done.actor.delete-project' &&
isInAProject &&
decodeURIComponent(location.pathname).includes(event.output.name)
if (isInDeletedProject) {
navigate(PATHS.HOME)
return
}
const isInRenamedProject =
event.type === 'xstate.done.actor.rename-project' &&
isInAProject &&
decodeURIComponent(location.pathname).includes(
event.output.oldName
)
if (isInRenamedProject) {
// TODO: In future, we can navigate to the new project path
// directly, but we need to coordinate with
// @lf94's useFileSystemWatcher in SettingsAuthProvider.tsx:224
// Because it's beating us to the punch and updating the route
// const newPathName = location.pathname.replace(
// encodeURIComponent(event.output.oldName),
// encodeURIComponent(event.output.newName)
// )
// navigate(newPathName)
return
}
}
},
toastSuccess: ({ event }) =>
toast.success(
('data' in event && typeof event.data === 'string' && event.data) ||
('output' in event &&
'message' in event.output &&
typeof event.output.message === 'string' &&
event.output.message) ||
''
),
toastError: ({ event }) =>
toast.error(
('data' in event && typeof event.data === 'string' && event.data) ||
('output' in event &&
typeof event.output === 'string' &&
event.output) ||
''
),
},
actors: {
readProjects: fromPromise(() => listProjects()),
createProject: fromPromise(async ({ input }) => {
let name = (
input && 'name' in input && input.name
? input.name
: settings.projects.defaultProjectName.current
).trim()
if (doesProjectNameNeedInterpolated(name)) {
const nextIndex = getNextProjectIndex(name, input.projects)
name = interpolateProjectNameWithIndex(name, nextIndex)
}
await createNewProjectDirectory(name)
return {
message: `Successfully created "${name}"`,
name,
}
}),
renameProject: fromPromise(async ({ input }) => {
const {
oldName,
newName,
defaultProjectName,
defaultDirectory,
projects,
} = input
let name = newName ? newName : defaultProjectName
if (doesProjectNameNeedInterpolated(name)) {
const nextIndex = getNextProjectIndex(name, projects)
name = interpolateProjectNameWithIndex(name, nextIndex)
}
console.log('from Project')
await renameProjectDirectory(
window.electron.path.join(defaultDirectory, oldName),
name
)
return {
message: `Successfully renamed "${oldName}" to "${name}"`,
oldName: oldName,
newName: name,
}
}),
deleteProject: fromPromise(async ({ input }) => {
await window.electron.rm(
window.electron.path.join(input.defaultDirectory, input.name),
{
recursive: true,
}
)
return {
message: `Successfully deleted "${input.name}"`,
name: input.name,
}
}),
},
guards: {
'Has at least 1 project': ({ event }) => {
if (event.type !== 'xstate.done.actor.read-projects') return false
console.log(`from has at least 1 project: ${event.output.length}`)
return event.output.length ? event.output.length >= 1 : false
},
},
}),
{
input: {
projects: projectPaths,
defaultProjectName: settings.projects.defaultProjectName.current,
defaultDirectory: settings.app.projectDirectory.current,
},
}
)
useEffect(() => {
send({ type: 'Read projects', data: {} })
}, [projectPaths])
// register all project-related command palette commands
useStateMachineCommands({
machineId: 'projects',
send,
state,
commandBarConfig: projectsCommandBarConfig,
actor,
})
return (
<ProjectsMachineContext.Provider
value={{
state,
send,
}}
>
{children}
</ProjectsMachineContext.Provider>
)
}

View File

@ -15,10 +15,7 @@ import { SettingsFieldInput } from './SettingsFieldInput'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { APP_VERSION } from 'routes/Settings' import { APP_VERSION } from 'routes/Settings'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { import { createAndOpenNewProject, getSettingsFolderPaths } from 'lib/desktopFS'
createAndOpenNewTutorialProject,
getSettingsFolderPaths,
} from 'lib/desktopFS'
import { useDotDotSlash } from 'hooks/useDotDotSlash' import { useDotDotSlash } from 'hooks/useDotDotSlash'
import { ForwardedRef, forwardRef, useEffect } from 'react' import { ForwardedRef, forwardRef, useEffect } from 'react'
import { useLspContext } from 'components/LspProvider' import { useLspContext } from 'components/LspProvider'
@ -82,7 +79,7 @@ export const AllSettingsFields = forwardRef(
} else { } else {
// If we're in the global settings, create a new project and navigate // If we're in the global settings, create a new project and navigate
// to the onboarding start in that project // to the onboarding start in that project
await createAndOpenNewTutorialProject({ onProjectOpen, navigate }) await createAndOpenNewProject({ onProjectOpen, navigate })
} }
} }
} }

View File

@ -41,7 +41,6 @@ import { reportRejection } from 'lib/trap'
import { getAppSettingsFilePath } from 'lib/desktop' import { getAppSettingsFilePath } from 'lib/desktop'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { codeManager } from 'lib/singletons'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -201,13 +200,13 @@ export const SettingsAuthProviderBase = ({
console.error('Error executing AST after settings change', e) console.error('Error executing AST after settings change', e)
} }
}, },
async persistSettings({ context, event }) { persistSettings: ({ context, event }) => {
// Without this, when a user changes the file, it'd // Without this, when a user changes the file, it'd
// create a detection loop with the file-system watcher. // create a detection loop with the file-system watcher.
if (event.doNotPersist) return if (event.doNotPersist) return
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true // eslint-disable-next-line @typescript-eslint/no-floating-promises
return saveSettings(context, loadedProject?.project?.path) saveSettings(context, loadedProject?.project?.path)
}, },
}, },
}), }),
@ -221,7 +220,7 @@ export const SettingsAuthProviderBase = ({
}, []) }, [])
useFileSystemWatcher( useFileSystemWatcher(
async (eventType: string) => { async () => {
// If there is a projectPath but it no longer exists it means // If there is a projectPath but it no longer exists it means
// it was exterally removed. If we let the code past this condition // it was exterally removed. If we let the code past this condition
// execute it will recreate the directory due to code in // execute it will recreate the directory due to code in
@ -235,9 +234,6 @@ export const SettingsAuthProviderBase = ({
} }
} }
// Only reload if there are changes. Ignore everything else.
if (eventType !== 'change') 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',

View File

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

View File

@ -14,7 +14,6 @@ import {
import { TransformInfo } from 'lang/std/stdTypes' import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
export function setEqualLengthInfo({ export function setEqualLengthInfo({
selectionRanges, selectionRanges,
@ -87,7 +86,7 @@ export function applyConstraintEqualLength({
selectionRanges: Selections selectionRanges: Selections
}): }):
| { | {
modifiedAst: Node<Program> modifiedAst: Program
pathToNodeMap: PathToNodeMap pathToNodeMap: PathToNodeMap
} }
| Error { | Error {

View File

@ -13,7 +13,6 @@ import {
import { TransformInfo } from 'lang/std/stdTypes' import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
export function horzVertInfo( export function horzVertInfo(
selectionRanges: Selections, selectionRanges: Selections,
@ -56,11 +55,11 @@ export function horzVertInfo(
export function applyConstraintHorzVert( export function applyConstraintHorzVert(
selectionRanges: Selections, selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal', horOrVert: 'vertical' | 'horizontal',
ast: Node<Program>, ast: Program,
programMemory: ProgramMemory programMemory: ProgramMemory
): ):
| { | {
modifiedAst: Node<Program> modifiedAst: Program
pathToNodeMap: PathToNodeMap pathToNodeMap: PathToNodeMap
} }
| Error { | Error {

View File

@ -19,7 +19,6 @@ import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
const getModalInfo = createInfoModal(GetInfoModal) const getModalInfo = createInfoModal(GetInfoModal)
@ -137,7 +136,7 @@ export async function applyConstraintIntersect({
}: { }: {
selectionRanges: Selections selectionRanges: Selections
}): Promise<{ }): Promise<{
modifiedAst: Node<Program> modifiedAst: Program
pathToNodeMap: PathToNodeMap pathToNodeMap: PathToNodeMap
}> { }> {
const info = intersectInfo({ const info = intersectInfo({

View File

@ -13,7 +13,6 @@ import {
import { TransformInfo } from 'lang/std/stdTypes' import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
export function removeConstrainingValuesInfo({ export function removeConstrainingValuesInfo({
selectionRanges, selectionRanges,
@ -78,7 +77,7 @@ export function applyRemoveConstrainingValues({
pathToNodes?: Array<PathToNode> pathToNodes?: Array<PathToNode>
}): }):
| { | {
modifiedAst: Node<Program> modifiedAst: Program
pathToNodeMap: PathToNodeMap pathToNodeMap: PathToNodeMap
} }
| Error { | Error {

View File

@ -23,7 +23,6 @@ import {
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal) const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
@ -162,7 +161,7 @@ export function applyConstraintAxisAlign({
constraint: 'snapToYAxis' | 'snapToXAxis' constraint: 'snapToYAxis' | 'snapToXAxis'
}): }):
| { | {
modifiedAst: Node<Program> modifiedAst: Program
pathToNodeMap: PathToNodeMap pathToNodeMap: PathToNodeMap
} }
| Error { | Error {

View File

@ -18,7 +18,6 @@ import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { cleanErrs, err } from 'lib/trap' import { cleanErrs, err } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
const getModalInfo = createInfoModal(GetInfoModal) const getModalInfo = createInfoModal(GetInfoModal)
@ -186,7 +185,7 @@ export function applyConstraintHorzVertAlign({
constraint: 'setHorzDistance' | 'setVertDistance' constraint: 'setHorzDistance' | 'setVertDistance'
}): }):
| { | {
modifiedAst: Node<Program> modifiedAst: Program
pathToNodeMap: PathToNodeMap pathToNodeMap: PathToNodeMap
} }
| Error { | Error {

View File

@ -1,4 +1,4 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { fireEvent, render, screen } from '@testing-library/react'
import UserSidebarMenu from './UserSidebarMenu' import UserSidebarMenu from './UserSidebarMenu'
import { import {
Route, Route,
@ -13,7 +13,7 @@ import { CommandBarProvider } from './CommandBar/CommandBarProvider'
type User = Models['User_type'] type User = Models['User_type']
describe('UserSidebarMenu tests', () => { describe('UserSidebarMenu tests', () => {
test("Renders user's name and email if available", async () => { test("Renders user's name and email if available", () => {
const userWellFormed: User = { const userWellFormed: User = {
id: '8675309', id: '8675309',
name: 'Test User', name: 'Test User',
@ -39,19 +39,13 @@ describe('UserSidebarMenu tests', () => {
fireEvent.click(screen.getByTestId('user-sidebar-toggle')) fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
await waitFor(() => { expect(screen.getByTestId('username')).toHaveTextContent(
expect(screen.getByTestId('username')).toHaveTextContent( userWellFormed.name || ''
userWellFormed.name || '' )
) expect(screen.getByTestId('email')).toHaveTextContent(userWellFormed.email)
})
await waitFor(() => {
expect(screen.getByTestId('email')).toHaveTextContent(
userWellFormed.email
)
})
}) })
test("Renders just the user's email if no name is available", async () => { test("Renders just the user's email if no name is available", () => {
const userNoName: User = { const userNoName: User = {
id: '8675309', id: '8675309',
email: 'kittycad.sidebar.test@example.com', email: 'kittycad.sidebar.test@example.com',
@ -77,12 +71,10 @@ describe('UserSidebarMenu tests', () => {
fireEvent.click(screen.getByTestId('user-sidebar-toggle')) fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
await waitFor(() => { expect(screen.getByTestId('username')).toHaveTextContent(userNoName.email)
expect(screen.getByTestId('username')).toHaveTextContent(userNoName.email)
})
}) })
test('Renders a menu button if no user avatar is available', async () => { test('Renders a menu button if no user avatar is available', () => {
const userNoAvatar: User = { const userNoAvatar: User = {
id: '8675309', id: '8675309',
name: 'Test User', name: 'Test User',
@ -106,11 +98,9 @@ describe('UserSidebarMenu tests', () => {
</TestWrap> </TestWrap>
) )
await waitFor(() => { expect(screen.getByTestId('user-sidebar-toggle')).toHaveTextContent(
expect(screen.getByTestId('user-sidebar-toggle')).toHaveTextContent( 'User menu'
'User menu' )
)
})
}) })
}) })

View File

@ -96,10 +96,10 @@ export class KclPlugin implements PluginValue {
const newCode = viewUpdate.state.doc.toString() const newCode = viewUpdate.state.doc.toString()
codeManager.code = newCode codeManager.code = newCode
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.writeToFile()
void codeManager.writeToFile().then(() => { this.scheduleUpdateDoc()
this.scheduleUpdateDoc()
})
} }
scheduleUpdateDoc() { scheduleUpdateDoc() {

View File

@ -1,6 +0,0 @@
import { ProjectsMachineContext } from 'components/ProjectsContextProvider'
import { useContext } from 'react'
export const useProjectsContext = () => {
return useContext(ProjectsMachineContext)
}

View File

@ -26,7 +26,6 @@ export function useRefreshSettings(routeId: string = PATHS.INDEX) {
ctx.settings.send({ ctx.settings.send({
type: 'Set all settings', type: 'Set all settings',
settings: routeData, settings: routeData,
doNotPersist: true,
}) })
}, []) }, [])
} }

View File

@ -5,7 +5,7 @@ import { useCommandsContext } from './useCommandsContext'
import { modelingMachine } from 'machines/modelingMachine' import { modelingMachine } from 'machines/modelingMachine'
import { authMachine } from 'machines/authMachine' import { authMachine } from 'machines/authMachine'
import { settingsMachine } from 'machines/settingsMachine' import { settingsMachine } from 'machines/settingsMachine'
import { projectsMachine } from 'machines/projectsMachine' import { homeMachine } from 'machines/homeMachine'
import { import {
Command, Command,
StateMachineCommandSetConfig, StateMachineCommandSetConfig,
@ -22,7 +22,7 @@ export type AllMachines =
| typeof modelingMachine | typeof modelingMachine
| typeof settingsMachine | typeof settingsMachine
| typeof authMachine | typeof authMachine
| typeof projectsMachine | typeof homeMachine
interface UseStateMachineCommandsArgs< interface UseStateMachineCommandsArgs<
T extends AllMachines, T extends AllMachines,

View File

@ -21,10 +21,9 @@ import {
import { getNodeFromPath } from './queryAst' import { getNodeFromPath } from './queryAst'
import { codeManager, editorManager, sceneInfra } from 'lib/singletons' import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
import { Diagnostic } from '@codemirror/lint' import { Diagnostic } from '@codemirror/lint'
import { Node } from 'wasm-lib/kcl/bindings/Node'
interface ExecuteArgs { interface ExecuteArgs {
ast?: Node<Program> ast?: Program
zoomToFit?: boolean zoomToFit?: boolean
executionId?: number executionId?: number
zoomOnRangeAndType?: { zoomOnRangeAndType?: {
@ -34,13 +33,13 @@ interface ExecuteArgs {
} }
export class KclManager { export class KclManager {
private _ast: Node<Program> = { private _ast: Program = {
body: [], body: [],
start: 0, start: 0,
end: 0, end: 0,
nonCodeMeta: { nonCodeMeta: {
nonCodeNodes: {}, nonCodeNodes: {},
startNodes: [], start: [],
}, },
} }
private _execState: ExecState = emptyExecState() private _execState: ExecState = emptyExecState()
@ -56,7 +55,7 @@ export class KclManager {
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
private _isExecutingCallback: (arg: boolean) => void = () => {} private _isExecutingCallback: (arg: boolean) => void = () => {}
private _astCallBack: (arg: Node<Program>) => void = () => {} private _astCallBack: (arg: Program) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {} private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {} private _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {} private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
@ -182,7 +181,7 @@ export class KclManager {
setWasmInitFailed, setWasmInitFailed,
}: { }: {
setProgramMemory: (arg: ProgramMemory) => void setProgramMemory: (arg: ProgramMemory) => void
setAst: (arg: Node<Program>) => void setAst: (arg: Program) => void
setLogs: (arg: string[]) => void setLogs: (arg: string[]) => void
setKclErrors: (arg: KCLError[]) => void setKclErrors: (arg: KCLError[]) => void
setIsExecuting: (arg: boolean) => void setIsExecuting: (arg: boolean) => void
@ -206,12 +205,12 @@ export class KclManager {
end: 0, end: 0,
nonCodeMeta: { nonCodeMeta: {
nonCodeNodes: {}, nonCodeNodes: {},
startNodes: [], start: [],
}, },
} }
} }
safeParse(code: string): Node<Program> | null { safeParse(code: string): Program | null {
const ast = parse(code) const ast = parse(code)
this.lints = [] this.lints = []
this.kclErrors = [] this.kclErrors = []
@ -378,7 +377,7 @@ export class KclManager {
Array.from(this.engineCommandManager.artifactGraph).forEach( Array.from(this.engineCommandManager.artifactGraph).forEach(
([commandId, artifact]) => { ([commandId, artifact]) => {
if (!('codeRef' in artifact)) return if (!('codeRef' in artifact)) return
const _node1 = getNodeFromPath<Node<CallExpression>>( const _node1 = getNodeFromPath<CallExpression>(
this.ast, this.ast,
artifact.codeRef.pathToNode, artifact.codeRef.pathToNode,
'CallExpression' 'CallExpression'
@ -429,16 +428,20 @@ export class KclManager {
// Update the code state and the editor. // Update the code state and the editor.
codeManager.updateCodeStateEditor(code) codeManager.updateCodeStateEditor(code)
// Write back to the file system. // Write back to the file system.
void codeManager.writeToFile().then(() => this.executeCode()) // eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.writeToFile()
// execute the code.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.executeCode()
} }
// There's overlapping responsibility between updateAst and executeAst. // There's overlapping responsibility between updateAst and executeAst.
// updateAst was added as it was used a lot before xState migration so makes the port easier. // updateAst was added as it was used a lot before xState migration so makes the port easier.
// but should probably have think about which of the function to keep // but should probably have think about which of the function to keep
// This always updates the code state and editor and writes to the file system. // This always updates the code state and editor and writes to the file system.
async updateAst( async updateAst(
ast: Node<Program>, ast: Program,
execute: boolean, execute: boolean,
optionalParams?: { optionalParams?: {
focusPath?: Array<PathToNode> focusPath?: Array<PathToNode>
@ -449,7 +452,7 @@ export class KclManager {
} }
} }
): Promise<{ ): Promise<{
newAst: Node<Program> newAst: Program
selections?: Selections selections?: Selections
}> { }> {
const newCode = recast(ast) const newCode = recast(ast)
@ -585,7 +588,7 @@ export class KclManager {
} }
// Determines if there is no KCL code which means it is executing a blank KCL file // Determines if there is no KCL code which means it is executing a blank KCL file
_isAstEmpty(ast: Node<Program>) { _isAstEmpty(ast: Program) {
return ast.start === 0 && ast.end === 0 && ast.body.length === 0 return ast.start === 0 && ast.end === 0 && ast.body.length === 0
} }
} }

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