Compare commits
60 Commits
mike/engin
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
57e030a0ad | |||
0adecf4f58 | |||
26e995dc3f | |||
08e411e1a3 | |||
a8b816a3e2 | |||
43bec115c0 | |||
0c6c646fe7 | |||
0d52851da2 | |||
6b105897f7 | |||
9ff51de301 | |||
c161f578fd | |||
4804eedf3e | |||
99db31a6a4 | |||
90b57ec202 | |||
3f86f99f5e | |||
83e2b093a6 | |||
58f7e0086d | |||
c147b3bfa2 | |||
7103ded32a | |||
81279aa4e8 | |||
550c8ae165 | |||
05610bb0f3 | |||
4a62862ca0 | |||
a4783d4951 | |||
30cfac06b8 | |||
c5509dabb1 | |||
239ab6850e | |||
4a7dd6e650 | |||
af2609e678 | |||
30909dedda | |||
39d76ed54f | |||
4925251c29 | |||
9772869545 | |||
a7e830cd02 | |||
ca102116b6 | |||
c2fba89e77 | |||
7e31678ba2 | |||
1140ced121 | |||
32b7ddaa7c | |||
2525f99515 | |||
4b8ce34b31 | |||
6617f72373 | |||
e9033e1754 | |||
9b697e30cf | |||
a70facdab4 | |||
4083f9f3dd | |||
7ead2bb875 | |||
19d01c563e | |||
dfe7cfc91c | |||
01443e445d | |||
e16eb49f51 | |||
5d5138e8e6 | |||
e1d6e29523 | |||
49657ad2e5 | |||
b40d353994 | |||
62ffa53add | |||
64dce4d8b1 | |||
02588b2672 | |||
3d1ac2ac0b | |||
ff5ce29fd7 |
50
.github/workflows/build-test-publish-apps.yml
vendored
@ -15,6 +15,7 @@ on:
|
|||||||
env:
|
env:
|
||||||
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||||
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||||
|
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
@ -25,7 +26,6 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.export_version.outputs.version }}
|
version: ${{ steps.export_version.outputs.version }}
|
||||||
notes: ${{ steps.export_version.outputs.notes }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@ -55,8 +55,6 @@ jobs:
|
|||||||
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
|
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
|
||||||
|
|
||||||
- name: Generate release notes
|
- name: Generate release notes
|
||||||
env:
|
|
||||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "$NOTES" > release-notes.md
|
echo "$NOTES" > release-notes.md
|
||||||
cat release-notes.md
|
cat release-notes.md
|
||||||
@ -84,9 +82,6 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
electron-builder.yml
|
electron-builder.yml
|
||||||
|
|
||||||
- id: export_notes
|
|
||||||
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Prepare electron-builder.yml file for updater test
|
- name: Prepare electron-builder.yml file for updater test
|
||||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
@ -114,17 +109,8 @@ 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
|
||||||
|
|
||||||
@ -186,8 +172,25 @@ jobs:
|
|||||||
smksp_cert_sync.exe
|
smksp_cert_sync.exe
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
||||||
- name: Build the app
|
- name: Build the app (debug)
|
||||||
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
|
if: ${{ env.BUILD_RELEASE == 'false' }}
|
||||||
|
# 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
|
||||||
@ -231,7 +234,17 @@ 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' }}
|
||||||
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
|
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
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||||
@ -262,7 +275,6 @@ jobs:
|
|||||||
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||||
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
||||||
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
||||||
NOTES: ${{ needs.prepare-files.outputs.notes }}
|
|
||||||
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
|
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
|
||||||
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
|
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
|
||||||
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
||||||
|
2
.github/workflows/cargo-check.yml
vendored
@ -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 --all --features disable-println --features pyo3 --features cli
|
cargo check --workspace --features disable-println --features pyo3 --features cli
|
||||||
|
2
.github/workflows/cargo-test.yml
vendored
@ -62,7 +62,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
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
|
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
|
||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
RUST_MIN_STACK: 10485760000
|
RUST_MIN_STACK: 10485760000
|
||||||
|
@ -74,10 +74,12 @@ 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)
|
||||||
|
60
docs/kcl/polygon.md
Normal file
38
docs/kcl/push.md
Normal file
62778
docs/kcl/std.json
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,6 @@ layout: manual
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `bindings` |`object`| | No |
|
| `bindings` |`object`| | No |
|
||||||
| `parent` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
| `parent` |`integer`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -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` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
| `start` |`integer`| | No |
|
||||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
| `end` |`integer`| | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -16,7 +16,7 @@ layout: manual
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `nonCodeNodes` |`object`| | No |
|
| `nonCodeNodes` |`object`| | No |
|
||||||
| `start` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | No |
|
| `startNodes` |`[` [`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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -162,6 +162,28 @@ 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 |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
24
docs/kcl/types/PolygonData.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
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 |
|
||||||
|
|
||||||
|
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -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` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
| `currentEnv` |`integer`| | No |
|
||||||
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |
|
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ A sketch is a collection of paths.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
|
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
|
||||||
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||||
|
@ -25,8 +25,8 @@ A sketch is a collection of paths.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `sketch`| | No |
|
| `type` |enum: `sketch`| | No |
|
||||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
|
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
|
||||||
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||||
|
@ -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` |[`BasePath`](/docs/kcl/types/BasePath)| The path the tag is on. | No |
|
| `path` |[`Path`](/docs/kcl/types/Path)| 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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -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}], %)
|
||||||
|> line([-${commonPoints.num2}, 0], %)`)
|
|> lineTo([0, ${commonPoints.num3}], %)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
|
@ -3,6 +3,7 @@ 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,
|
||||||
@ -114,20 +115,15 @@ test.describe('when using the file tree to', () => {
|
|||||||
async ({ browser: _, tronApp }, testInfo) => {
|
async ({ browser: _, tronApp }, testInfo) => {
|
||||||
await tronApp.initialise()
|
await tronApp.initialise()
|
||||||
|
|
||||||
const {
|
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } =
|
||||||
panesOpen,
|
await getUtils(tronApp.page, test)
|
||||||
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 createAndSelectProject('project-000')
|
await createProject({ name: 'project-000', page: tronApp.page })
|
||||||
|
|
||||||
// File the main.kcl with contents
|
// File the main.kcl with contents
|
||||||
const kclCube = await fsp.readFile(
|
const kclCube = await fsp.readFile(
|
||||||
@ -167,15 +163,14 @@ test.describe('when using the file tree to', () => {
|
|||||||
async ({ browser: _, tronApp }, testInfo) => {
|
async ({ browser: _, tronApp }, testInfo) => {
|
||||||
await tronApp.initialise()
|
await tronApp.initialise()
|
||||||
|
|
||||||
const { panesOpen, createAndSelectProject, createNewFile } =
|
const { panesOpen, createNewFile } = await getUtils(tronApp.page, test)
|
||||||
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 createAndSelectProject('project-000')
|
await createProject({ name: 'project-000', page: tronApp.page })
|
||||||
|
|
||||||
await createNewFile('')
|
await createNewFile('')
|
||||||
await createNewFile('')
|
await createNewFile('')
|
||||||
@ -198,62 +193,74 @@ 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 ({ browser: _, tronApp }, testInfo) => {
|
async (
|
||||||
await tronApp.initialise()
|
{ browser: _, tronApp, homePage, scene, editor, toolbar },
|
||||||
|
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 tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
await test.step(`Setup: Open project and navigate to ${secondFile}`, async () => {
|
||||||
tronApp.page.on('console', console.log)
|
await homePage.expectState({
|
||||||
|
projectCards: [
|
||||||
await createAndSelectProject('project-000')
|
{
|
||||||
await openKclCodePanel()
|
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 openFilePanel()
|
||||||
// File the main.kcl with contents
|
await selectFile(secondFile)
|
||||||
const kclCube = await fsp.readFile(
|
})
|
||||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
await pasteCodeInEditor(kclCube)
|
|
||||||
|
|
||||||
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
|
await test.step(`Attempt to rename ${secondFile} to ${mainFile}`, async () => {
|
||||||
await tronApp.page.waitForTimeout(2000)
|
await renameFile(secondFile, mainFile)
|
||||||
|
})
|
||||||
|
|
||||||
const kcl1 = 'main.kcl'
|
await test.step(`Postcondition: ${mainFile} still has the original content`, async () => {
|
||||||
const kcl2 = '2.kcl'
|
await selectFile(mainFile)
|
||||||
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: ${kcl2} still exists with the original content`, async () => {
|
await test.step(`Postcondition: ${secondFile} still exists with the original content`, async () => {
|
||||||
await selectFile(kcl2)
|
await selectFile(secondFile)
|
||||||
await editorTextMatches(kclCylinder)
|
await editorTextMatches(kclCylinder)
|
||||||
})
|
})
|
||||||
|
|
||||||
await tronApp?.close?.()
|
await tronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -263,20 +270,15 @@ test.describe('when using the file tree to', () => {
|
|||||||
async ({ browser: _, tronApp }, testInfo) => {
|
async ({ browser: _, tronApp }, testInfo) => {
|
||||||
await tronApp.initialise()
|
await tronApp.initialise()
|
||||||
|
|
||||||
const {
|
const { panesOpen, pasteCodeInEditor, deleteFile, editorTextMatches } =
|
||||||
panesOpen,
|
await getUtils(tronApp.page, _test)
|
||||||
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 createAndSelectProject('project-000')
|
await createProject({ name: 'project-000', page: tronApp.page })
|
||||||
// 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',
|
||||||
@ -284,11 +286,11 @@ test.describe('when using the file tree to', () => {
|
|||||||
)
|
)
|
||||||
await pasteCodeInEditor(kclCube)
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
const kcl1 = 'main.kcl'
|
const mainFile = 'main.kcl'
|
||||||
|
|
||||||
await deleteFile(kcl1)
|
await deleteFile(mainFile)
|
||||||
|
|
||||||
await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => {
|
await test.step(`Postcondition: ${mainFile} is recreated but has no content`, async () => {
|
||||||
await editorTextMatches('')
|
await editorTextMatches('')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -306,7 +308,6 @@ test.describe('when using the file tree to', () => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
panesOpen,
|
panesOpen,
|
||||||
createAndSelectProject,
|
|
||||||
pasteCodeInEditor,
|
pasteCodeInEditor,
|
||||||
createNewFile,
|
createNewFile,
|
||||||
openDebugPanel,
|
openDebugPanel,
|
||||||
@ -318,7 +319,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 createAndSelectProject('project-000')
|
await createProject({ name: 'project-000', page: tronApp.page })
|
||||||
|
|
||||||
// Create a small file
|
// Create a small file
|
||||||
const kclCube = await fsp.readFile(
|
const kclCube = await fsp.readFile(
|
||||||
@ -722,7 +723,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(60000)
|
await page.waitForTimeout(1000)
|
||||||
await folderToRename.click({ button: 'right' })
|
await folderToRename.click({ button: 'right' })
|
||||||
await _expect(renameMenuItem).toBeVisible()
|
await _expect(renameMenuItem).toBeVisible()
|
||||||
await renameMenuItem.click()
|
await renameMenuItem.click()
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
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 { sansWhitespace } from '../test-utils'
|
import {
|
||||||
|
closePane,
|
||||||
|
checkIfPaneIsOpen,
|
||||||
|
openPane,
|
||||||
|
sansWhitespace,
|
||||||
|
} from '../test-utils'
|
||||||
|
|
||||||
interface EditorState {
|
interface EditorState {
|
||||||
activeLines: Array<string>
|
activeLines: Array<string>
|
||||||
@ -11,6 +16,7 @@ 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
|
||||||
@ -31,19 +37,32 @@ 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) {
|
||||||
return expectStart.not.toContainText(code, { timeout })
|
const result = await expectStart.not.toContainText(code, { timeout })
|
||||||
|
await resetPane()
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
return expectStart.toContainText(code, { timeout })
|
const result = await 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(
|
||||||
@ -56,9 +75,13 @@ export class EditorFixture {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (not) {
|
if (not) {
|
||||||
return expectStart.not.toContain(normalisedCode)
|
const result = await expectStart.not.toContain(normalisedCode)
|
||||||
|
await resetPane()
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
return expectStart.toContain(normalisedCode)
|
const result = await expectStart.toContain(normalisedCode)
|
||||||
|
await resetPane()
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
expectEditor = {
|
expectEditor = {
|
||||||
toContain: this._expectEditorToContain(),
|
toContain: this._expectEditorToContain(),
|
||||||
@ -115,4 +138,13 @@ 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ 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
|
||||||
@ -36,7 +37,7 @@ export class AuthenticatedApp {
|
|||||||
;(window as any).playwrightSkipFilePicker = true
|
;(window as any).playwrightSkipFilePicker = true
|
||||||
}, code)
|
}, code)
|
||||||
|
|
||||||
await this.page.setViewportSize({ width: 1000, height: 500 })
|
await this.page.setViewportSize(this.viewPortSize)
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,13 @@ 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 = {
|
||||||
@ -20,6 +26,13 @@ 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
|
||||||
|
|
||||||
@ -55,7 +68,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) {
|
||||||
@ -78,6 +91,47 @@ 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.
|
||||||
*
|
*
|
||||||
|
@ -7,6 +7,7 @@ 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
|
||||||
@ -24,6 +25,7 @@ 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')
|
||||||
|
@ -7,6 +7,7 @@ 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'
|
||||||
@ -55,6 +56,48 @@ 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')`
|
||||||
|
|
||||||
@ -378,7 +421,9 @@ test(
|
|||||||
const restartConfirmationButton = page.getByRole('button', {
|
const restartConfirmationButton = page.getByRole('button', {
|
||||||
name: 'Make a new project',
|
name: 'Make a new project',
|
||||||
})
|
})
|
||||||
const tutorialProjectIndicator = page.getByText('Tutorial Project 00')
|
const tutorialProjectIndicator = page
|
||||||
|
.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')
|
||||||
|
@ -451,3 +451,103 @@ 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
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
Paths,
|
Paths,
|
||||||
setupElectron,
|
setupElectron,
|
||||||
tearDown,
|
tearDown,
|
||||||
createProjectAndRenameIt,
|
createProject,
|
||||||
} 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,21 +503,261 @@ 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}/router-template-slate`, { recursive: true })
|
await fsp.mkdir(`${dir}/${projectName}`, { 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}/router-template-slate/main.kcl`
|
`${dir}/${projectName}/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}/router-template-slate/otherThingToClickOn.kcl`
|
`${dir}/${projectName}/otherThingToClickOn.kcl`
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -526,7 +766,7 @@ test(
|
|||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
await page.getByText('router-template-slate').click()
|
await page.getByText(projectName).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,
|
||||||
@ -643,7 +883,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.getByText('bracket')
|
const project = page.getByTestId('project-link').getByText('bracket')
|
||||||
|
|
||||||
await project.hover()
|
await project.hover()
|
||||||
await project.focus()
|
await project.focus()
|
||||||
@ -687,10 +927,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 page.getByRole('button', { name: 'New project' }).click()
|
await createProject({ name: 'project-000', page, returnHome: true })
|
||||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
await expect(
|
||||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
page.getByTestId('project-link').filter({ hasText: 'project-000' })
|
||||||
await expect(page.getByText('project-000')).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
@ -867,17 +1107,16 @@ 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 page.getByRole('button', { name: 'New project' }).click()
|
await createProject({ name: 'project-000', page, returnHome: true })
|
||||||
|
await expect(projectLinks.getByText('project-000')).toBeVisible()
|
||||||
|
|
||||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
await projectLinks.getByText('project-000').click()
|
||||||
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()
|
||||||
|
|
||||||
@ -936,16 +1175,10 @@ 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++) {
|
||||||
await createProject(i)
|
const name = `project-${i.toString().padStart(3, '0')}`
|
||||||
|
await createProject({ name, page, returnHome: true })
|
||||||
|
await expect(projectLinks.getByText(name)).toBeVisible()
|
||||||
}
|
}
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
@ -1120,11 +1353,10 @@ 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 page.getByRole('button', { name: 'New project' }).click()
|
await createProject({ name: 'project-000', page, returnHome: true })
|
||||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
await expect(
|
||||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
page.getByTestId('project-link').filter({ hasText: 'project-000' })
|
||||||
|
).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 () => {
|
||||||
@ -1265,6 +1497,7 @@ test(
|
|||||||
'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',
|
||||||
@ -1450,7 +1683,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 createProjectAndRenameIt({ name: 'wrist brace', page })
|
await createProject({ name: 'wrist brace', page, returnHome: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Should go through onboarding', async () => {
|
await test.step('Should go through onboarding', async () => {
|
||||||
|
@ -637,7 +637,6 @@ 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)
|
||||||
@ -675,15 +674,16 @@ test.describe('Sketch tests', () => {
|
|||||||
|
|
||||||
await click00r(50, 0)
|
await click00r(50, 0)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
codeStr += ` |> line(${toSU([50, 0])}, %)`
|
codeStr += ` |> lineTo(${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)
|
||||||
|
|
||||||
await click00r(-50, 0)
|
let clickCoords = await click00r(-50, 0)
|
||||||
codeStr += ` |> line(${toSU([-50, 0])}, %)`
|
expect(clickCoords).not.toBeUndefined()
|
||||||
|
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,8 +709,10 @@ 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 += ` |> line([2.04, 0], %)`
|
codeStr += ` |> lineTo([4.07, 0], %)`
|
||||||
await expect(u.codeLocator).toHaveText(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(0, 30)
|
await click00r(0, 30)
|
||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
@ -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], %)
|
||||||
|> line([-12.34, 0], %)
|
|> lineTo([0, -12.34], %)
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
@ -45,7 +45,9 @@ 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
|
||||||
@ -118,15 +120,32 @@ async function waitForDefaultPlanesToBeVisible(page: Page) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openPane(page: Page, testId: string) {
|
export async function checkIfPaneIsOpen(page: Page, testId: string) {
|
||||||
const locator = page.getByTestId(testId)
|
const paneButtonLocator = page.getByTestId(testId)
|
||||||
await expect(locator).toBeVisible()
|
await expect(paneButtonLocator).toBeVisible()
|
||||||
const isOpen = (await locator?.getAttribute('aria-pressed')) === 'true'
|
return (await paneButtonLocator?.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 locator.click()
|
await paneButtonLocator.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) {
|
||||||
@ -467,20 +486,6 @@ 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 })
|
||||||
@ -520,6 +525,9 @@ 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
|
||||||
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -888,7 +896,17 @@ export async function setupElectron({
|
|||||||
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
|
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
|
||||||
const settingsOverrides = TOML.stringify(
|
const settingsOverrides = TOML.stringify(
|
||||||
appSettings
|
appSettings
|
||||||
? { settings: appSettings }
|
? {
|
||||||
|
settings: {
|
||||||
|
...TEST_SETTINGS,
|
||||||
|
...appSettings,
|
||||||
|
app: {
|
||||||
|
...TEST_SETTINGS.app,
|
||||||
|
projectDirectory: projectDirName,
|
||||||
|
...appSettings.app,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
: {
|
: {
|
||||||
settings: {
|
settings: {
|
||||||
...TEST_SETTINGS,
|
...TEST_SETTINGS,
|
||||||
@ -970,30 +988,25 @@ export async function isOutOfViewInScrollContainer(
|
|||||||
return isOutOfView
|
return isOutOfView
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createProjectAndRenameIt({
|
export async function createProject({
|
||||||
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()
|
||||||
|
|
||||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
if (returnHome) {
|
||||||
await page.getByText(`project-000`).hover()
|
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||||
await page.getByText(`project-000`).focus()
|
await page.getByTestId('app-logo').click()
|
||||||
|
}
|
||||||
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 {
|
||||||
|
@ -292,7 +292,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
|||||||
await test.step(`Verify the camera moved`, async () => {
|
await test.step(`Verify the camera moved`, async () => {
|
||||||
await scene.expectState({
|
await scene.expectState({
|
||||||
camera: {
|
camera: {
|
||||||
position: [0, -23865.37, 11073.54],
|
position: [0, -23865.37, 11073.53],
|
||||||
target: [0, 0, 0],
|
target: [0, 0, 0],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -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}], %)
|
||||||
|> line([-${commonPoints.num2}, 0], %)`)
|
|> lineTo([0, ${commonPoints.num3}], %)`)
|
||||||
|
|
||||||
// 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,7 +157,9 @@ 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.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
await page
|
||||||
|
.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()
|
||||||
@ -180,7 +182,9 @@ test.describe('Testing selections', () => {
|
|||||||
process.platform === 'linux' ? 'Control' : 'Meta'
|
process.platform === 'linux' ? 'Control' : 'Meta'
|
||||||
)
|
)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
await page
|
||||||
|
.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)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
|
import * as fs from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import {
|
import {
|
||||||
getUtils,
|
getUtils,
|
||||||
@ -7,6 +8,7 @@ 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'
|
||||||
@ -265,10 +267,15 @@ 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 { electronApp, page } = await setupElectron({
|
const projectName = 'bracket'
|
||||||
|
const {
|
||||||
|
electronApp,
|
||||||
|
page,
|
||||||
|
dir: projectDirName,
|
||||||
|
} = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
const bracketDir = join(dir, 'bracket')
|
const bracketDir = join(dir, projectName)
|
||||||
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'),
|
||||||
@ -280,6 +287,12 @@ 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', {
|
||||||
@ -292,6 +305,21 @@ test.describe('Testing settings', () => {
|
|||||||
const projectLink = page.getByText('bracket')
|
const projectLink = page.getByText('bracket')
|
||||||
const logoLink = page.getByTestId('app-logo')
|
const logoLink = page.getByTestId('app-logo')
|
||||||
|
|
||||||
|
async function confirmThemeWasWritten(filePath: string, value: string) {
|
||||||
|
return expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const fileExists = await fs.existsSync(filePath)
|
||||||
|
return fileExists ? fsp.readFile(filePath, 'utf-8') : ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Setting should now be written to the file',
|
||||||
|
timeout: 5_000,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toContain(`themeColor = "${value}"`)
|
||||||
|
}
|
||||||
|
|
||||||
await test.step('Set user theme color on home', async () => {
|
await test.step('Set user theme color on home', async () => {
|
||||||
await expect(settingsOpenButton).toBeVisible()
|
await expect(settingsOpenButton).toBeVisible()
|
||||||
await settingsOpenButton.click()
|
await settingsOpenButton.click()
|
||||||
@ -299,6 +327,7 @@ test.describe('Testing settings', () => {
|
|||||||
await expect(userSettingsTab).toBeChecked()
|
await expect(userSettingsTab).toBeChecked()
|
||||||
await themeColorSetting.fill(userThemeColor)
|
await themeColorSetting.fill(userThemeColor)
|
||||||
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
|
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
|
||||||
|
await confirmThemeWasWritten(tempUserSettingsFilePath, userThemeColor)
|
||||||
await settingsCloseButton.click()
|
await settingsCloseButton.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -310,6 +339,11 @@ test.describe('Testing settings', () => {
|
|||||||
await expect(projectSettingsTab).toBeChecked()
|
await expect(projectSettingsTab).toBeChecked()
|
||||||
await themeColorSetting.fill(projectThemeColor)
|
await themeColorSetting.fill(projectThemeColor)
|
||||||
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
||||||
|
// Make sure that the project settings file has been written to before continuing
|
||||||
|
await confirmThemeWasWritten(
|
||||||
|
tempProjectSettingsFilePath,
|
||||||
|
projectThemeColor
|
||||||
|
)
|
||||||
await settingsCloseButton.click()
|
await settingsCloseButton.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -323,6 +357,7 @@ test.describe('Testing settings', () => {
|
|||||||
|
|
||||||
await test.step(`Navigate back to the home view and see user setting applied`, async () => {
|
await test.step(`Navigate back to the home view and see user setting applied`, async () => {
|
||||||
await logoLink.click()
|
await logoLink.click()
|
||||||
|
await page.screenshot({ path: 'out.png' })
|
||||||
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
|
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -428,9 +463,7 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Check color of logo changed when in modeling view', async () => {
|
await test.step('Check color of logo changed when in modeling view', async () => {
|
||||||
await page.getByRole('button', { name: 'New project' }).click()
|
await createProject({ name: 'project-000', page })
|
||||||
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')
|
||||||
})
|
})
|
||||||
@ -448,7 +481,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,
|
||||||
@ -466,11 +499,7 @@ test.describe('Testing settings', () => {
|
|||||||
await expect(projectDirLink).toBeVisible()
|
await expect(projectDirLink).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
const projectLinks = page.getByTestId('project-link')
|
await createProject({ name: 'project-000', page })
|
||||||
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(
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page } from '@playwright/test'
|
||||||
import { getUtils, setup, tearDown, setupElectron } from './test-utils'
|
import {
|
||||||
|
getUtils,
|
||||||
|
setup,
|
||||||
|
tearDown,
|
||||||
|
setupElectron,
|
||||||
|
createProject,
|
||||||
|
} from './test-utils'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
@ -700,17 +706,17 @@ test(
|
|||||||
const fileExists = () =>
|
const fileExists = () =>
|
||||||
fs.existsSync(join(dir, projectName, textToCadFileName))
|
fs.existsSync(join(dir, projectName, textToCadFileName))
|
||||||
|
|
||||||
const {
|
const { openFilePanel, openKclCodePanel, waitForPageLoad } = await getUtils(
|
||||||
createAndSelectProject,
|
page,
|
||||||
openFilePanel,
|
test
|
||||||
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.getByRole('button', { name: projectName })
|
const projectMenuButton = page
|
||||||
|
.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 }),
|
||||||
})
|
})
|
||||||
@ -719,7 +725,7 @@ test(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Create and navigate to the project
|
// Create and navigate to the project
|
||||||
await createAndSelectProject('project-000')
|
await createProject({ name: 'project-000', page })
|
||||||
|
|
||||||
// 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
@ -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 'lib/machineManager'
|
import { MachinesListing } from 'components/MachineManagerProvider'
|
||||||
|
|
||||||
type EnvFn = (value?: string) => string
|
type EnvFn = (value?: string) => string
|
||||||
|
|
||||||
|
@ -107,6 +107,13 @@
|
|||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
"loaded_filament_idx": {
|
||||||
|
"description": "The currently loaded filament index.",
|
||||||
|
"format": "uint",
|
||||||
|
"minimum": 0,
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"nozzle_diameter": {
|
"nozzle_diameter": {
|
||||||
"description": "Diameter of the extrusion nozzle, in mm.",
|
"description": "Diameter of the extrusion nozzle, in mm.",
|
||||||
"format": "double",
|
"format": "double",
|
||||||
@ -285,6 +292,21 @@
|
|||||||
"type"
|
"type"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Unknown material",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"unknown"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -974,7 +996,7 @@
|
|||||||
},
|
},
|
||||||
"description": "",
|
"description": "",
|
||||||
"title": "machine-api",
|
"title": "machine-api",
|
||||||
"version": "0.1.0"
|
"version": "0.1.1"
|
||||||
},
|
},
|
||||||
"openapi": "3.0.3",
|
"openapi": "3.0.3",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zoo-modeling-app",
|
"name": "zoo-modeling-app",
|
||||||
"version": "0.26.0",
|
"version": "0.26.2",
|
||||||
"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.26.1",
|
"react-router-dom": "^6.27.0",
|
||||||
"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 --all && cargo clippy --all --tests --benches)",
|
"test:rust": "(cd src/wasm-lib && cargo test --workspace && cargo clippy --workspace --all-targets)",
|
||||||
"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.5.0",
|
"@types/node": "^22.7.8",
|
||||||
"@types/pixelmatch": "^5.2.6",
|
"@types/pixelmatch": "^5.2.6",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
|
@ -34,6 +34,11 @@
|
|||||||
"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",
|
||||||
@ -54,6 +59,11 @@
|
|||||||
"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",
|
||||||
@ -61,7 +71,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "gear.kcl",
|
"file": "gear.kcl",
|
||||||
"title": "Gear",
|
"title": "Spur 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."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
0
release-notes.md
Normal file
@ -21,6 +21,7 @@ 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,
|
||||||
@ -42,6 +43,7 @@ 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
|
||||||
|
|
||||||
@ -49,17 +51,22 @@ 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>
|
||||||
|
<MachineManagerProvider>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
</MachineManagerProvider>
|
||||||
</AppStateProvider>
|
</AppStateProvider>
|
||||||
</KclContextProvider>
|
</KclContextProvider>
|
||||||
|
</ProjectsContextProvider>
|
||||||
</LspProvider>
|
</LspProvider>
|
||||||
</SettingsAuthProvider>
|
</SettingsAuthProvider>
|
||||||
</CommandBarProvider>
|
</CommandBarProvider>
|
||||||
|
@ -100,6 +100,11 @@ 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:
|
||||||
@ -113,10 +118,11 @@ export function Toolbar({
|
|||||||
typeof maybeIconConfig.hotkey === 'string'
|
typeof maybeIconConfig.hotkey === 'string'
|
||||||
? maybeIconConfig.hotkey
|
? maybeIconConfig.hotkey
|
||||||
: maybeIconConfig.hotkey?.(state),
|
: maybeIconConfig.hotkey?.(state),
|
||||||
disabled:
|
disabled: isDisabled,
|
||||||
disableAllButtons ||
|
disabledReason:
|
||||||
maybeIconConfig.status !== 'available' ||
|
typeof maybeIconConfig.disabledReason === 'function'
|
||||||
maybeIconConfig.disabled?.(state) === true,
|
? maybeIconConfig.disabledReason(state)
|
||||||
|
: maybeIconConfig.disabledReason,
|
||||||
disableHotkey: maybeIconConfig.disableHotkey?.(state),
|
disableHotkey: maybeIconConfig.disableHotkey?.(state),
|
||||||
status: maybeIconConfig.status,
|
status: maybeIconConfig.status,
|
||||||
}
|
}
|
||||||
@ -273,6 +279,8 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
|
|||||||
itemConfig: ToolbarItemResolved
|
itemConfig: ToolbarItemResolved
|
||||||
configCallbackProps: ToolbarItemCallbackProps
|
configCallbackProps: ToolbarItemCallbackProps
|
||||||
}) {
|
}) {
|
||||||
|
const { state } = useModelingContext()
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
itemConfig.hotkey || '',
|
itemConfig.hotkey || '',
|
||||||
() => {
|
() => {
|
||||||
@ -336,6 +344,17 @@ 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" />
|
||||||
|
@ -92,6 +92,7 @@ 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
|
||||||
@ -233,6 +234,7 @@ export class CameraControls {
|
|||||||
this.target = new Vector3()
|
this.target = new Vector3()
|
||||||
this.domElement = domElement
|
this.domElement = domElement
|
||||||
this.isDragging = false
|
this.isDragging = false
|
||||||
|
this.wasDragging = false
|
||||||
this.mouseDownPosition = new Vector2()
|
this.mouseDownPosition = new Vector2()
|
||||||
this.mouseNewPosition = new Vector2()
|
this.mouseNewPosition = new Vector2()
|
||||||
|
|
||||||
@ -363,6 +365,8 @@ export class CameraControls {
|
|||||||
onMouseDown = (event: PointerEvent) => {
|
onMouseDown = (event: PointerEvent) => {
|
||||||
this.domElement.setPointerCapture(event.pointerId)
|
this.domElement.setPointerCapture(event.pointerId)
|
||||||
this.isDragging = true
|
this.isDragging = true
|
||||||
|
// Reset the wasDragging flag to false when starting a new drag
|
||||||
|
this.wasDragging = false
|
||||||
this.mouseDownPosition.set(event.clientX, event.clientY)
|
this.mouseDownPosition.set(event.clientX, event.clientY)
|
||||||
let interaction = this.getInteractionType(event)
|
let interaction = this.getInteractionType(event)
|
||||||
if (interaction === 'none') return
|
if (interaction === 'none') return
|
||||||
@ -392,6 +396,10 @@ 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])
|
||||||
@ -399,6 +407,7 @@ 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') {
|
||||||
@ -427,6 +436,9 @@ export class CameraControls {
|
|||||||
* under the cursor. This recently moved from being handled in App.tsx.
|
* under the cursor. This recently moved from being handled in App.tsx.
|
||||||
* This might not be the right spot, but it is more consolidated.
|
* This might not be the right spot, but it is more consolidated.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Clear any previous drag state
|
||||||
|
this.wasDragging = false
|
||||||
if (this.syncDirection === 'engineToClient') {
|
if (this.syncDirection === 'engineToClient') {
|
||||||
const newCmdId = uuidv4()
|
const newCmdId = uuidv4()
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ 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)
|
||||||
@ -201,7 +202,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<CallExpression>(
|
const _node1 = getNodeFromPath<Node<CallExpression>>(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
overlay.pathToNode,
|
overlay.pathToNode,
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
@ -381,7 +382,7 @@ export async function deleteSegment({
|
|||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
sketchDetails: SketchDetails | null
|
sketchDetails: SketchDetails | null
|
||||||
}) {
|
}) {
|
||||||
let modifiedAst: Program | Error = kclManager.ast
|
let modifiedAst: Node<Program> | Error = kclManager.ast
|
||||||
const dependentRanges = findUsesOfTagInPipe(modifiedAst, pathToNode)
|
const dependentRanges = findUsesOfTagInPipe(modifiedAst, pathToNode)
|
||||||
|
|
||||||
const shouldContinueSegDelete = dependentRanges.length
|
const shouldContinueSegDelete = dependentRanges.length
|
||||||
|
@ -19,6 +19,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
ARROWHEAD,
|
ARROWHEAD,
|
||||||
AXIS_GROUP,
|
AXIS_GROUP,
|
||||||
|
DRAFT_POINT,
|
||||||
|
DRAFT_POINT_GROUP,
|
||||||
getSceneScale,
|
getSceneScale,
|
||||||
INTERSECTION_PLANE_LAYER,
|
INTERSECTION_PLANE_LAYER,
|
||||||
OnClickCallbackArgs,
|
OnClickCallbackArgs,
|
||||||
@ -53,7 +55,7 @@ import {
|
|||||||
editorManager,
|
editorManager,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { executeAst } from 'lang/langHelpers'
|
import { executeAst, ToolTip } from 'lang/langHelpers'
|
||||||
import {
|
import {
|
||||||
createProfileStartHandle,
|
createProfileStartHandle,
|
||||||
SegmentUtils,
|
SegmentUtils,
|
||||||
@ -92,6 +94,7 @@ 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'
|
||||||
|
|
||||||
@ -313,6 +316,27 @@ 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,
|
||||||
@ -321,30 +345,106 @@ export class SceneEntities {
|
|||||||
sketchDetails: SketchDetails
|
sketchDetails: SketchDetails
|
||||||
afterClick: (args: OnClickCallbackArgs) => void
|
afterClick: (args: OnClickCallbackArgs) => void
|
||||||
}) {
|
}) {
|
||||||
// Create a THREEjs plane to raycast clicks onto
|
// TODO: Consolidate shared logic between this and setupSketch
|
||||||
|
// 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
|
||||||
if (this.intersectionPlane) {
|
this.intersectionPlane!.setRotationFromQuaternion(quaternion)
|
||||||
this.intersectionPlane.setRotationFromQuaternion(quaternion)
|
this.intersectionPlane!.position.copy(
|
||||||
this.intersectionPlane.position.copy(
|
|
||||||
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
|
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,
|
||||||
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
|
[snappedClickPoint.x, snappedClickPoint.y]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (trap(addStartProfileAtRes)) return
|
if (trap(addStartProfileAtRes)) return
|
||||||
@ -352,6 +452,7 @@ 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)
|
||||||
@ -369,14 +470,14 @@ export class SceneEntities {
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
}: {
|
}: {
|
||||||
sketchPathToNode: PathToNode
|
sketchPathToNode: PathToNode
|
||||||
maybeModdedAst: Program
|
maybeModdedAst: Node<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: Program
|
truncatedAst: Node<Program>
|
||||||
programMemoryOverride: ProgramMemory
|
programMemoryOverride: ProgramMemory
|
||||||
sketch: Sketch
|
sketch: Sketch
|
||||||
variableDeclarationName: string
|
variableDeclarationName: string
|
||||||
@ -407,7 +508,7 @@ export class SceneEntities {
|
|||||||
if (err(sketch)) return Promise.reject(sketch)
|
if (err(sketch)) return Promise.reject(sketch)
|
||||||
if (!sketch) return Promise.reject('sketch not found')
|
if (!sketch) return Promise.reject('sketch not found')
|
||||||
|
|
||||||
if (!isArray(sketch?.value))
|
if (!isArray(sketch?.paths))
|
||||||
return {
|
return {
|
||||||
truncatedAst,
|
truncatedAst,
|
||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
@ -424,24 +525,20 @@ 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 orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const scale = sceneInfra.getClientSceneScaleFactor(dummy)
|
||||||
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?.value?.[0]?.type !== 'Circle') {
|
if (sketch?.paths?.[0]?.type !== 'Circle') {
|
||||||
const _profileStart = createProfileStartHandle({
|
const _profileStart = createProfileStartHandle({
|
||||||
from: sketch.start.from,
|
from: sketch.start.from,
|
||||||
id: sketch.start.__geoMeta.id,
|
id: sketch.start.__geoMeta.id,
|
||||||
pathToNode: segPathToNode,
|
pathToNode: segPathToNode,
|
||||||
scale: factor,
|
scale,
|
||||||
theme: sceneInfra._theme,
|
theme: sceneInfra._theme,
|
||||||
|
isDraft: false,
|
||||||
})
|
})
|
||||||
_profileStart.layers.set(SKETCH_LAYER)
|
_profileStart.layers.set(SKETCH_LAYER)
|
||||||
_profileStart.traverse((child) => {
|
_profileStart.traverse((child) => {
|
||||||
@ -451,16 +548,16 @@ export class SceneEntities {
|
|||||||
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
|
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
|
||||||
}
|
}
|
||||||
const callbacks: (() => SegmentOverlayPayload | null)[] = []
|
const callbacks: (() => SegmentOverlayPayload | null)[] = []
|
||||||
sketch.value.forEach((segment, index) => {
|
sketch.paths.forEach((segment, index) => {
|
||||||
let segPathToNode = getNodePathFromSourceRange(
|
let segPathToNode = getNodePathFromSourceRange(
|
||||||
maybeModdedAst,
|
maybeModdedAst,
|
||||||
segment.__geoMeta.sourceRange
|
segment.__geoMeta.sourceRange
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
draftExpressionsIndices &&
|
draftExpressionsIndices &&
|
||||||
(sketch.value[index - 1] || sketch.start)
|
(sketch.paths[index - 1] || sketch.start)
|
||||||
) {
|
) {
|
||||||
const previousSegment = sketch.value[index - 1] || sketch.start
|
const previousSegment = sketch.paths[index - 1] || sketch.start
|
||||||
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
||||||
maybeModdedAst,
|
maybeModdedAst,
|
||||||
previousSegment.__geoMeta.sourceRange
|
previousSegment.__geoMeta.sourceRange
|
||||||
@ -511,13 +608,13 @@ export class SceneEntities {
|
|||||||
to: segment.to,
|
to: segment.to,
|
||||||
}
|
}
|
||||||
const result = initSegment({
|
const result = initSegment({
|
||||||
prevSegment: sketch.value[index - 1],
|
prevSegment: sketch.paths[index - 1],
|
||||||
callExpName,
|
callExpName,
|
||||||
input,
|
input,
|
||||||
id: segment.__geoMeta.id,
|
id: segment.__geoMeta.id,
|
||||||
pathToNode: segPathToNode,
|
pathToNode: segPathToNode,
|
||||||
isDraftSegment,
|
isDraftSegment,
|
||||||
scale: factor,
|
scale,
|
||||||
texture: sceneInfra.extraSegmentTexture,
|
texture: sceneInfra.extraSegmentTexture,
|
||||||
theme: sceneInfra._theme,
|
theme: sceneInfra._theme,
|
||||||
isSelected,
|
isSelected,
|
||||||
@ -561,7 +658,7 @@ export class SceneEntities {
|
|||||||
}
|
}
|
||||||
updateAstAndRejigSketch = async (
|
updateAstAndRejigSketch = async (
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
modifiedAst: Program | Error,
|
modifiedAst: Node<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]
|
||||||
@ -610,9 +707,9 @@ export class SceneEntities {
|
|||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
)
|
)
|
||||||
if (err(sg)) return Promise.reject(sg)
|
if (err(sg)) return Promise.reject(sg)
|
||||||
const lastSeg = sg?.value?.slice(-1)[0] || sg.start
|
const lastSeg = sg?.paths?.slice(-1)[0] || sg.start
|
||||||
|
|
||||||
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
||||||
const mod = addNewSketchLn({
|
const mod = addNewSketchLn({
|
||||||
node: _ast,
|
node: _ast,
|
||||||
programMemory: kclManager.programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
@ -645,16 +742,24 @@ export class SceneEntities {
|
|||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onClick: async (args) => {
|
onClick: async (args) => {
|
||||||
if (!args) return
|
if (!args) return
|
||||||
|
// If there is a valid camera interaction that matches, do that instead
|
||||||
|
const interaction = sceneInfra.camControls.getInteractionType(
|
||||||
|
args.mouseEvent
|
||||||
|
)
|
||||||
|
if (interaction !== 'none') return
|
||||||
if (args.mouseEvent.which !== 1) return
|
if (args.mouseEvent.which !== 1) return
|
||||||
|
|
||||||
const { intersectionPoint } = args
|
const { intersectionPoint } = args
|
||||||
let intersection2d = intersectionPoint?.twoD
|
let intersection2d = intersectionPoint?.twoD
|
||||||
const profileStart = args.intersects
|
const intersectsProfileStart = 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) {
|
|
||||||
const lastSegment = sketch.value.slice(-1)[0]
|
// Snapping logic for the profile start handle
|
||||||
|
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,
|
||||||
@ -686,19 +791,39 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||||
} else if (intersection2d) {
|
} else if (intersection2d) {
|
||||||
const lastSegment = sketch.value.slice(-1)[0]
|
const intersectsYAxis = args.intersects.find(
|
||||||
|
(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: [intersection2d.x, intersection2d.y],
|
to: [snappedPoint.x, snappedPoint.y],
|
||||||
},
|
},
|
||||||
fnName:
|
fnName: resolvedFunctionName,
|
||||||
lastSegment.type === 'TangentialArcTo'
|
|
||||||
? 'tangentialArcTo'
|
|
||||||
: 'line',
|
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
})
|
})
|
||||||
if (trap(tmp)) return Promise.reject(tmp)
|
if (trap(tmp)) return Promise.reject(tmp)
|
||||||
@ -710,7 +835,7 @@ export class SceneEntities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await kclManager.executeAstMock(modifiedAst)
|
await kclManager.executeAstMock(modifiedAst)
|
||||||
if (profileStart) {
|
if (intersectsProfileStart) {
|
||||||
sceneInfra.modelingSend({ type: 'CancelSketch' })
|
sceneInfra.modelingSend({ type: 'CancelSketch' })
|
||||||
} else {
|
} else {
|
||||||
await this.setUpDraftSegment(
|
await this.setUpDraftSegment(
|
||||||
@ -735,7 +860,6 @@ export class SceneEntities {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
...this.mouseEnterLeaveCallbacks(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setupDraftRectangle = async (
|
setupDraftRectangle = async (
|
||||||
@ -817,7 +941,7 @@ export class SceneEntities {
|
|||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
)
|
)
|
||||||
if (err(sketch)) return Promise.reject(sketch)
|
if (err(sketch)) return Promise.reject(sketch)
|
||||||
const sgPaths = sketch.value
|
const sgPaths = sketch.paths
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
||||||
@ -826,6 +950,11 @@ export class SceneEntities {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick: async (args) => {
|
onClick: async (args) => {
|
||||||
|
// If there is a valid camera interaction that matches, do that instead
|
||||||
|
const interaction = sceneInfra.camControls.getInteractionType(
|
||||||
|
args.mouseEvent
|
||||||
|
)
|
||||||
|
if (interaction !== 'none') return
|
||||||
// Commit the rectangle to the full AST/code and return to sketch.idle
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
||||||
const cornerPoint = args.intersectionPoint?.twoD
|
const cornerPoint = args.intersectionPoint?.twoD
|
||||||
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
||||||
@ -868,7 +997,7 @@ export class SceneEntities {
|
|||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
)
|
)
|
||||||
if (err(sketch)) return
|
if (err(sketch)) return
|
||||||
const sgPaths = sketch.value
|
const sgPaths = sketch.paths
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
// Update the starting segment of the THREEjs scene
|
// Update the starting segment of the THREEjs scene
|
||||||
@ -985,7 +1114,7 @@ export class SceneEntities {
|
|||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
)
|
)
|
||||||
if (err(sketch)) return
|
if (err(sketch)) return
|
||||||
const sgPaths = sketch.value
|
const sgPaths = sketch.paths
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
||||||
@ -994,6 +1123,11 @@ export class SceneEntities {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick: async (args) => {
|
onClick: async (args) => {
|
||||||
|
// If there is a valid camera interaction that matches, do that instead
|
||||||
|
const interaction = sceneInfra.camControls.getInteractionType(
|
||||||
|
args.mouseEvent
|
||||||
|
)
|
||||||
|
if (interaction !== 'none') return
|
||||||
// Commit the rectangle to the full AST/code and return to sketch.idle
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
||||||
const cornerPoint = args.intersectionPoint?.twoD
|
const cornerPoint = args.intersectionPoint?.twoD
|
||||||
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
||||||
@ -1105,7 +1239,7 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
|
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
|
||||||
if (addingNewSegmentStatus === 'nothing') {
|
if (addingNewSegmentStatus === 'nothing') {
|
||||||
const prevSegment = sketch.value[pipeIndex - 2]
|
const prevSegment = sketch.paths[pipeIndex - 2]
|
||||||
const mod = addNewSketchLn({
|
const mod = addNewSketchLn({
|
||||||
node: kclManager.ast,
|
node: kclManager.ast,
|
||||||
programMemory: kclManager.programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
@ -1157,6 +1291,11 @@ export class SceneEntities {
|
|||||||
},
|
},
|
||||||
onMove: () => {},
|
onMove: () => {},
|
||||||
onClick: (args) => {
|
onClick: (args) => {
|
||||||
|
// If there is a valid camera interaction that matches, do that instead
|
||||||
|
const interaction = sceneInfra.camControls.getInteractionType(
|
||||||
|
args.mouseEvent
|
||||||
|
)
|
||||||
|
if (interaction !== 'none') return
|
||||||
if (args?.mouseEvent.which !== 1) return
|
if (args?.mouseEvent.which !== 1) return
|
||||||
if (!args || !args.selected) {
|
if (!args || !args.selected) {
|
||||||
sceneInfra.modelingSend({
|
sceneInfra.modelingSend({
|
||||||
@ -1177,7 +1316,7 @@ export class SceneEntities {
|
|||||||
}
|
}
|
||||||
prepareTruncatedMemoryAndAst = (
|
prepareTruncatedMemoryAndAst = (
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
ast?: Program,
|
ast?: Node<Program>,
|
||||||
draftSegment?: DraftSegment
|
draftSegment?: DraftSegment
|
||||||
) =>
|
) =>
|
||||||
prepareTruncatedMemoryAndAst(
|
prepareTruncatedMemoryAndAst(
|
||||||
@ -1198,20 +1337,35 @@ export class SceneEntities {
|
|||||||
sketchPathToNode: PathToNode
|
sketchPathToNode: PathToNode
|
||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
draftInfo?: {
|
draftInfo?: {
|
||||||
truncatedAst: Program
|
truncatedAst: Node<Program>
|
||||||
programMemoryOverride: ProgramMemory
|
programMemoryOverride: ProgramMemory
|
||||||
variableDeclarationName: string
|
variableDeclarationName: string
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
const profileStart =
|
const intersectsProfileStart =
|
||||||
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 = profileStart
|
const intersection2d = intersectsProfileStart
|
||||||
? new Vector2(profileStart.position.x, profileStart.position.y)
|
? new Vector2(
|
||||||
|
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
|
||||||
@ -1231,10 +1385,10 @@ export class SceneEntities {
|
|||||||
group.userData.from[0],
|
group.userData.from[0],
|
||||||
group.userData.from[1],
|
group.userData.from[1],
|
||||||
]
|
]
|
||||||
const dragTo: [number, number] = [intersection2d.x, intersection2d.y]
|
const dragTo: [number, number] = [snappedPoint.x, snappedPoint.y]
|
||||||
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
|
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
|
||||||
|
|
||||||
const _node = getNodeFromPath<CallExpression>(
|
const _node = getNodeFromPath<Node<CallExpression>>(
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
@ -1246,7 +1400,7 @@ export class SceneEntities {
|
|||||||
|
|
||||||
let modded:
|
let modded:
|
||||||
| {
|
| {
|
||||||
modifiedAst: Program
|
modifiedAst: Node<Program>
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
}
|
}
|
||||||
| Error
|
| Error
|
||||||
@ -1345,7 +1499,7 @@ export class SceneEntities {
|
|||||||
}
|
}
|
||||||
if (!sketch) return
|
if (!sketch) return
|
||||||
|
|
||||||
const sgPaths = sketch.value
|
const sgPaths = sketch.paths
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
this.updateSegment(
|
this.updateSegment(
|
||||||
@ -1393,7 +1547,7 @@ export class SceneEntities {
|
|||||||
modifiedAst,
|
modifiedAst,
|
||||||
segment.__geoMeta.sourceRange
|
segment.__geoMeta.sourceRange
|
||||||
)
|
)
|
||||||
const sgPaths = sketch.value
|
const sgPaths = sketch.paths
|
||||||
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
||||||
segPathToNode[1][0] = varDecIndex
|
segPathToNode[1][0] = varDecIndex
|
||||||
const pathToNodeStr = JSON.stringify(segPathToNode)
|
const pathToNodeStr = JSON.stringify(segPathToNode)
|
||||||
@ -1541,7 +1695,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<CallExpression>(
|
const _node = getNodeFromPath<Node<CallExpression>>(
|
||||||
updatedAst,
|
updatedAst,
|
||||||
parent.userData.pathToNode,
|
parent.userData.pathToNode,
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
@ -1676,12 +1830,12 @@ export type DefaultPlaneStr = 'XY' | 'XZ' | 'YZ' | '-XY' | '-XZ' | '-YZ'
|
|||||||
|
|
||||||
function prepareTruncatedMemoryAndAst(
|
function prepareTruncatedMemoryAndAst(
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
ast: Program,
|
ast: Node<Program>,
|
||||||
programMemory: ProgramMemory,
|
programMemory: ProgramMemory,
|
||||||
draftSegment?: DraftSegment
|
draftSegment?: DraftSegment
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
truncatedAst: Program
|
truncatedAst: Node<Program>
|
||||||
programMemoryOverride: ProgramMemory
|
programMemoryOverride: ProgramMemory
|
||||||
variableDeclarationName: string
|
variableDeclarationName: string
|
||||||
}
|
}
|
||||||
@ -1689,7 +1843,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<VariableDeclaration>(
|
const _node = getNodeFromPath<Node<VariableDeclaration>>(
|
||||||
_ast,
|
_ast,
|
||||||
sketchPathToNode || [],
|
sketchPathToNode || [],
|
||||||
'VariableDeclaration'
|
'VariableDeclaration'
|
||||||
@ -1701,7 +1855,7 @@ function prepareTruncatedMemoryAndAst(
|
|||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
)
|
)
|
||||||
if (err(sg)) return sg
|
if (err(sg)) return sg
|
||||||
const lastSeg = sg?.value.slice(-1)[0]
|
const lastSeg = sg?.paths.slice(-1)[0]
|
||||||
if (draftSegment) {
|
if (draftSegment) {
|
||||||
// truncatedAst needs to setup with another segment at the end
|
// truncatedAst needs to setup with another segment at the end
|
||||||
let newSegment
|
let newSegment
|
||||||
@ -1739,15 +1893,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 VariableDeclaration
|
const varDec = _ast.body[bodyIndex] as Node<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 PipeExpression
|
const init = declarator.init as Node<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: Program = {
|
const truncatedAst: Node<Program> = {
|
||||||
..._ast,
|
..._ast,
|
||||||
body: [structuredClone(_ast.body[bodyIndex])],
|
body: [structuredClone(_ast.body[bodyIndex])],
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ 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']
|
||||||
|
|
||||||
@ -49,6 +50,10 @@ 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'
|
||||||
@ -60,6 +65,11 @@ 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 {
|
||||||
@ -213,7 +223,7 @@ export class SceneInfra {
|
|||||||
to: Coords2d
|
to: Coords2d
|
||||||
angle?: number
|
angle?: number
|
||||||
}): SegmentOverlayPayload | null {
|
}): SegmentOverlayPayload | null {
|
||||||
if (group.userData.pathToNode && arrowGroup) {
|
if (!group.userData.draft && 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
|
||||||
@ -348,29 +358,42 @@ 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
|
||||||
)
|
)
|
||||||
const recastablePlaneIntersect = planeIntersects.find(
|
if (!planeIntersects.length) return null
|
||||||
|
|
||||||
|
// 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 (!planeIntersects.length) return null
|
if (!raycastablePlaneIntersection)
|
||||||
if (!recastablePlaneIntersect) return { intersection: planeIntersects[0] }
|
return { intersection: planeIntersects[0] }
|
||||||
const planePosition = planeIntersects[0].object.position
|
const planePosition = raycastablePlaneIntersection.object.position
|
||||||
const inversePlaneQuaternion = planeIntersects[0].object.quaternion
|
const inversePlaneQuaternion =
|
||||||
.clone()
|
raycastablePlaneIntersection.object.quaternion.clone().invert()
|
||||||
.invert()
|
const intersectPoint = raycastablePlaneIntersection.point
|
||||||
const intersectPoint = planeIntersects[0].point
|
|
||||||
let transformedPoint = intersectPoint.clone()
|
let transformedPoint = intersectPoint.clone()
|
||||||
if (transformedPoint) {
|
if (transformedPoint) {
|
||||||
transformedPoint.applyQuaternion(inversePlaneQuaternion)
|
transformedPoint.applyQuaternion(inversePlaneQuaternion)
|
||||||
@ -447,18 +470,26 @@ 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({
|
||||||
|
@ -45,6 +45,7 @@ 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,
|
||||||
@ -58,7 +59,7 @@ import { err } from 'lib/trap'
|
|||||||
|
|
||||||
interface CreateSegmentArgs {
|
interface CreateSegmentArgs {
|
||||||
input: SegmentInputs
|
input: SegmentInputs
|
||||||
prevSegment: Sketch['value'][number]
|
prevSegment: Sketch['paths'][number]
|
||||||
id: string
|
id: string
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
isDraftSegment?: boolean
|
isDraftSegment?: boolean
|
||||||
@ -72,7 +73,7 @@ interface CreateSegmentArgs {
|
|||||||
|
|
||||||
interface UpdateSegmentArgs {
|
interface UpdateSegmentArgs {
|
||||||
input: SegmentInputs
|
input: SegmentInputs
|
||||||
prevSegment: Sketch['value'][number]
|
prevSegment: Sketch['paths'][number]
|
||||||
group: Group
|
group: Group
|
||||||
sceneInfra: SceneInfra
|
sceneInfra: SceneInfra
|
||||||
scale?: number
|
scale?: number
|
||||||
@ -147,6 +148,7 @@ 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,
|
||||||
@ -347,6 +349,7 @@ 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,
|
||||||
@ -515,11 +518,18 @@ 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,
|
||||||
@ -532,7 +542,7 @@ class CircleSegment implements SegmentUtils {
|
|||||||
}
|
}
|
||||||
group.name = CIRCLE_SEGMENT
|
group.name = CIRCLE_SEGMENT
|
||||||
|
|
||||||
group.add(arcMesh, arrowGroup, circleCenterGroup)
|
group.add(arcMesh, arrowGroup, circleCenterGroup, radiusIndicatorGroup)
|
||||||
const updateOverlaysCallback = this.update({
|
const updateOverlaysCallback = this.update({
|
||||||
prevSegment,
|
prevSegment,
|
||||||
input,
|
input,
|
||||||
@ -564,6 +574,9 @@ 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
|
||||||
@ -581,11 +594,14 @@ class CircleSegment implements SegmentUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (arrowGroup) {
|
if (arrowGroup) {
|
||||||
arrowGroup.position.set(
|
// The arrowhead is placed at the perimeter of the circle,
|
||||||
center[0] + Math.cos(Math.PI / 4) * radius,
|
// pointing up and to the right
|
||||||
center[1] + Math.sin(Math.PI / 4) * radius,
|
const arrowPoint = {
|
||||||
0
|
x: center[0] + Math.cos(Math.PI / 4) * radius,
|
||||||
)
|
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(
|
||||||
@ -596,6 +612,31 @@ 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)
|
||||||
@ -646,19 +687,20 @@ class CircleSegment implements SegmentUtils {
|
|||||||
|
|
||||||
export function createProfileStartHandle({
|
export function createProfileStartHandle({
|
||||||
from,
|
from,
|
||||||
id,
|
isDraft = false,
|
||||||
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
|
||||||
@ -671,13 +713,12 @@ export function createProfileStartHandle({
|
|||||||
|
|
||||||
group.userData = {
|
group.userData = {
|
||||||
type: PROFILE_START,
|
type: PROFILE_START,
|
||||||
id,
|
|
||||||
from,
|
from,
|
||||||
pathToNode,
|
|
||||||
isSelected,
|
isSelected,
|
||||||
baseColor,
|
baseColor,
|
||||||
|
...rest,
|
||||||
}
|
}
|
||||||
group.name = PROFILE_START
|
group.name = isDraft ? DRAFT_POINT : 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
|
||||||
|
@ -140,6 +140,13 @@ const FileTreeItem = ({
|
|||||||
async (eventType, path) => {
|
async (eventType, path) => {
|
||||||
// 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') {
|
||||||
|
// Prevents a cyclic read / write causing editor problems such as
|
||||||
|
// misplaced cursor positions.
|
||||||
|
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
|
||||||
|
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
||||||
code = normalizeLineEndings(code)
|
code = normalizeLineEndings(code)
|
||||||
codeManager.updateCodeStateEditor(code)
|
codeManager.updateCodeStateEditor(code)
|
||||||
@ -531,3 +538,19 @@ 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -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 { createAndOpenNewProject } from 'lib/desktopFS'
|
import { createAndOpenNewTutorialProject } 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,9 +116,10 @@ export function HelpMenu(props: React.PropsWithChildren) {
|
|||||||
if (isInProject) {
|
if (isInProject) {
|
||||||
navigate(filePath + PATHS.ONBOARDING.INDEX)
|
navigate(filePath + PATHS.ONBOARDING.INDEX)
|
||||||
} else {
|
} else {
|
||||||
createAndOpenNewProject({ onProjectOpen, navigate }).catch(
|
createAndOpenNewTutorialProject({
|
||||||
reportRejection
|
onProjectOpen,
|
||||||
)
|
navigate,
|
||||||
|
}).catch(reportRejection)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -23,6 +23,7 @@ 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'
|
||||||
|
|
||||||
|
123
src/components/MachineManagerProvider.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
@ -1,5 +1,11 @@
|
|||||||
import { useMachine } from '@xstate/react'
|
import { useMachine } from '@xstate/react'
|
||||||
import React, { createContext, useEffect, useMemo, useRef } from 'react'
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useContext,
|
||||||
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
Actor,
|
Actor,
|
||||||
AnyStateMachine,
|
AnyStateMachine,
|
||||||
@ -28,7 +34,7 @@ import {
|
|||||||
editorManager,
|
editorManager,
|
||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
import { machineManager } from 'lib/machineManager'
|
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||||
import {
|
import {
|
||||||
@ -85,6 +91,7 @@ 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>
|
||||||
@ -140,6 +147,8 @@ export const ModelingMachineProvider = ({
|
|||||||
// >
|
// >
|
||||||
// )
|
// )
|
||||||
|
|
||||||
|
const machineManager = useContext(MachineManagerContext)
|
||||||
|
|
||||||
const [modelingState, modelingSend, modelingActor] = useMachine(
|
const [modelingState, modelingSend, modelingActor] = useMachine(
|
||||||
modelingMachine.provide({
|
modelingMachine.provide({
|
||||||
actions: {
|
actions: {
|
||||||
@ -408,7 +417,7 @@ export const ModelingMachineProvider = ({
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Make: ({ event }) => {
|
Make: ({ context, 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) {
|
||||||
@ -422,7 +431,21 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the current machine.
|
// Set the current machine.
|
||||||
machineManager.currentMachine = event.data.machine
|
// Due to our use of singeton pattern, we need to do this to reliably
|
||||||
|
// 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',
|
||||||
@ -644,6 +667,7 @@ export const ModelingMachineProvider = ({
|
|||||||
input.plane
|
input.plane
|
||||||
)
|
)
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
|
sceneInfra.camControls.enableRotate = false
|
||||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||||
|
|
||||||
await letEngineAnimateAndSyncCamAfter(
|
await letEngineAnimateAndSyncCamAfter(
|
||||||
@ -948,7 +972,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 Program
|
parsed = parsed as Node<Program>
|
||||||
|
|
||||||
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
||||||
moveValueIntoNewVariablePath(
|
moveValueIntoNewVariablePath(
|
||||||
@ -959,7 +983,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 Program
|
parsed = parsed as Node<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'))
|
||||||
|
|
||||||
@ -994,6 +1018,7 @@ export const ModelingMachineProvider = ({
|
|||||||
...modelingMachineDefaultContext.store,
|
...modelingMachineDefaultContext.store,
|
||||||
...persistedContext,
|
...persistedContext,
|
||||||
},
|
},
|
||||||
|
machineManager,
|
||||||
},
|
},
|
||||||
// devTools: true,
|
// devTools: true,
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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'
|
||||||
@ -6,22 +7,24 @@ 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 {
|
||||||
extends React.PropsWithChildren,
|
id: string
|
||||||
React.HTMLAttributes<HTMLDivElement> {
|
children: ReactNode | ReactNode[]
|
||||||
|
className?: string
|
||||||
icon?: CustomIconName | IconDefinition
|
icon?: CustomIconName | IconDefinition
|
||||||
title: string
|
title: ReactNode
|
||||||
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, 'icon' | 'title' | 'Menu' | 'onClose'>) => {
|
}: Pick<ModelingPaneProps, 'id' | '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">
|
||||||
@ -34,7 +37,7 @@ export const ModelingPaneHeader = ({
|
|||||||
bgClassName="!bg-transparent"
|
bgClassName="!bg-transparent"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span>{title}</span>
|
<span data-testid={id + '-header'}>{title}</span>
|
||||||
</div>
|
</div>
|
||||||
{Menu instanceof Function ? <Menu /> : Menu}
|
{Menu instanceof Function ? <Menu /> : Menu}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@ -86,6 +89,7 @@ export const ModelingPane = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ModelingPaneHeader
|
<ModelingPaneHeader
|
||||||
|
id={id}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
title={title}
|
title={title}
|
||||||
Menu={Menu}
|
Menu={Menu}
|
||||||
|
@ -88,26 +88,31 @@ 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 (typeof val.value !== 'function') {
|
if (
|
||||||
const sg = sketchFromKclValue(val, null)
|
(val.type === 'UserVal' && val.value.type === 'Sketch') ||
|
||||||
|
// @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.value.map(({ __geoMeta, ...rest }: Path) => {
|
processedMemory[key] = sg.paths.map(({ __geoMeta, ...rest }: Path) => {
|
||||||
return rest
|
return rest
|
||||||
})
|
})
|
||||||
} else if ((val.type as any) === 'Function') {
|
} else {
|
||||||
|
processedMemory[key] = val.value
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
} else if (val.type === 'Function') {
|
||||||
processedMemory[key] = `__function(${(val as any)?.expression?.params
|
processedMemory[key] = `__function(${(val as any)?.expression?.params
|
||||||
?.map?.(({ identifier }: any) => identifier?.name || '')
|
?.map?.(({ identifier }: any) => identifier?.name || '')
|
||||||
.join(', ')})__`
|
.join(', ')})__`
|
||||||
} else {
|
} else {
|
||||||
processedMemory[key] = val.value
|
processedMemory[key] = val.value
|
||||||
}
|
}
|
||||||
} else if (key !== 'log') {
|
|
||||||
processedMemory[key] = '__function__'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return processedMemory
|
return processedMemory
|
||||||
}
|
}
|
||||||
|
@ -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 } from 'components/FileTree'
|
import { FileTreeInner, FileTreeMenu, FileTreeRoot } 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,7 +38,8 @@ interface PaneCallbackProps {
|
|||||||
|
|
||||||
export type SidebarPane = {
|
export type SidebarPane = {
|
||||||
id: SidebarType
|
id: SidebarType
|
||||||
title: string
|
title: ReactNode
|
||||||
|
sidebarName?: string
|
||||||
icon: CustomIconName | IconDefinition
|
icon: CustomIconName | IconDefinition
|
||||||
keybinding: string
|
keybinding: string
|
||||||
Content: ReactNode | React.FC
|
Content: ReactNode | React.FC
|
||||||
@ -49,7 +50,7 @@ export type SidebarPane = {
|
|||||||
|
|
||||||
export type SidebarAction = {
|
export type SidebarAction = {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: ReactNode
|
||||||
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
|
||||||
@ -78,7 +79,8 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'files',
|
id: 'files',
|
||||||
title: 'Project Files',
|
title: <FileTreeRoot />,
|
||||||
|
sidebarName: 'Project Files',
|
||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
Content: FileTreeInner,
|
Content: FileTreeInner,
|
||||||
keybinding: 'Shift + F',
|
keybinding: 'Shift + F',
|
@ -1,6 +1,13 @@
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { Resizable } from 're-resizable'
|
import { Resizable } from 're-resizable'
|
||||||
import { MouseEventHandler, useCallback, useEffect, useMemo } from 'react'
|
import {
|
||||||
|
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'
|
||||||
@ -13,7 +20,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 { machineManager } from 'lib/machineManager'
|
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -29,6 +36,7 @@ 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()
|
||||||
@ -263,7 +271,8 @@ interface ModelingPaneButtonProps
|
|||||||
extends React.HTMLAttributes<HTMLButtonElement> {
|
extends React.HTMLAttributes<HTMLButtonElement> {
|
||||||
paneConfig: {
|
paneConfig: {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: ReactNode
|
||||||
|
sidebarName?: string
|
||||||
icon: CustomIconName | IconDefinition
|
icon: CustomIconName | IconDefinition
|
||||||
keybinding: string
|
keybinding: string
|
||||||
iconClassName?: string
|
iconClassName?: string
|
||||||
@ -292,7 +301,10 @@ 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={paneConfig.title}
|
name={
|
||||||
|
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}
|
||||||
@ -308,7 +320,7 @@ function ModelingPaneButton({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
{paneConfig.title}
|
{paneConfig.sidebarName ?? paneConfig.title}
|
||||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||||
</span>
|
</span>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -317,7 +329,7 @@ function ModelingPaneButton({
|
|||||||
hoverOnly
|
hoverOnly
|
||||||
>
|
>
|
||||||
<span className="flex-1">
|
<span className="flex-1">
|
||||||
{paneConfig.title}
|
{paneConfig.sidebarName ?? paneConfig.title}
|
||||||
{disabledText !== undefined ? ` (${disabledText})` : ''}
|
{disabledText !== undefined ? ` (${disabledText})` : ''}
|
||||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
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 = ({
|
||||||
@ -9,9 +11,12 @@ export const NetworkMachineIndicator = ({
|
|||||||
}: {
|
}: {
|
||||||
className?: string
|
className?: string
|
||||||
}) => {
|
}) => {
|
||||||
const machineCount = machineManager.machineCount()
|
const {
|
||||||
const reason = machineManager.noMachinesReason()
|
noMachinesReason,
|
||||||
const machines = machineManager.machines
|
machines,
|
||||||
|
machines: { length: machineCount },
|
||||||
|
} = useContext(MachineManagerContext)
|
||||||
|
const reason = noMachinesReason()
|
||||||
|
|
||||||
return isDesktop() ? (
|
return isDesktop() ? (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
@ -47,7 +52,8 @@ 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((machine) => {
|
{machines.map(
|
||||||
|
(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>
|
||||||
@ -74,7 +80,8 @@ export const NetworkMachineIndicator = ({
|
|||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})}
|
}
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
|
@ -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 } from 'react'
|
import { Fragment, useMemo, useContext } 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 { machineManager } from 'lib/machineManager'
|
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||||
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,6 +96,8 @@ 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' }
|
||||||
@ -106,7 +108,7 @@ function ProjectMenuPopover({
|
|||||||
(c) => c.name === obj.name && c.groupId === obj.groupId
|
(c) => c.name === obj.name && c.groupId === obj.groupId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
const machineCount = machineManager.machineCount()
|
const machineCount = machineManager.machines.length
|
||||||
|
|
||||||
// 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')[]>(
|
||||||
|
289
src/components/ProjectsContextProvider.tsx
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
@ -15,7 +15,10 @@ 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 { createAndOpenNewProject, getSettingsFolderPaths } from 'lib/desktopFS'
|
import {
|
||||||
|
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'
|
||||||
@ -79,7 +82,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 createAndOpenNewProject({ onProjectOpen, navigate })
|
await createAndOpenNewTutorialProject({ onProjectOpen, navigate })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,10 +255,14 @@ export const Stream = () => {
|
|||||||
}, [mediaStream])
|
}, [mediaStream])
|
||||||
|
|
||||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
|
// If we've got no stream or connection, don't do anything
|
||||||
if (!isNetworkOkay) return
|
if (!isNetworkOkay) return
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
|
// If we're in sketch mode, don't send a engine-side select event
|
||||||
if (state.matches('Sketch')) return
|
if (state.matches('Sketch')) return
|
||||||
if (state.matches({ idle: 'showPlanes' })) return
|
if (state.matches({ idle: 'showPlanes' })) return
|
||||||
|
// If we're mousing up from a camera drag, don't send a select event
|
||||||
|
if (sceneInfra.camControls.wasDragging === true) return
|
||||||
|
|
||||||
if (btnName(e.nativeEvent).left) {
|
if (btnName(e.nativeEvent).left) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
@ -14,6 +14,7 @@ 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,
|
||||||
@ -86,7 +87,7 @@ export function applyConstraintEqualLength({
|
|||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
}):
|
}):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Program
|
modifiedAst: Node<Program>
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
}
|
}
|
||||||
| Error {
|
| Error {
|
||||||
|
@ -13,6 +13,7 @@ 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,
|
||||||
@ -55,11 +56,11 @@ export function horzVertInfo(
|
|||||||
export function applyConstraintHorzVert(
|
export function applyConstraintHorzVert(
|
||||||
selectionRanges: Selections,
|
selectionRanges: Selections,
|
||||||
horOrVert: 'vertical' | 'horizontal',
|
horOrVert: 'vertical' | 'horizontal',
|
||||||
ast: Program,
|
ast: Node<Program>,
|
||||||
programMemory: ProgramMemory
|
programMemory: ProgramMemory
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Program
|
modifiedAst: Node<Program>
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
}
|
}
|
||||||
| Error {
|
| Error {
|
||||||
|
@ -19,6 +19,7 @@ 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)
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ export async function applyConstraintIntersect({
|
|||||||
}: {
|
}: {
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
modifiedAst: Program
|
modifiedAst: Node<Program>
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
}> {
|
}> {
|
||||||
const info = intersectInfo({
|
const info = intersectInfo({
|
||||||
|
@ -13,6 +13,7 @@ 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,
|
||||||
@ -77,7 +78,7 @@ export function applyRemoveConstrainingValues({
|
|||||||
pathToNodes?: Array<PathToNode>
|
pathToNodes?: Array<PathToNode>
|
||||||
}):
|
}):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Program
|
modifiedAst: Node<Program>
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
}
|
}
|
||||||
| Error {
|
| Error {
|
||||||
|
@ -23,6 +23,7 @@ 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)
|
||||||
|
|
||||||
@ -161,7 +162,7 @@ export function applyConstraintAxisAlign({
|
|||||||
constraint: 'snapToYAxis' | 'snapToXAxis'
|
constraint: 'snapToYAxis' | 'snapToXAxis'
|
||||||
}):
|
}):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Program
|
modifiedAst: Node<Program>
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
}
|
}
|
||||||
| Error {
|
| Error {
|
||||||
|
@ -18,6 +18,7 @@ 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)
|
||||||
|
|
||||||
@ -185,7 +186,7 @@ export function applyConstraintHorzVertAlign({
|
|||||||
constraint: 'setHorzDistance' | 'setVertDistance'
|
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||||
}):
|
}):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Program
|
modifiedAst: Node<Program>
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
}
|
}
|
||||||
| Error {
|
| Error {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen, waitFor } 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", () => {
|
test("Renders user's name and email if available", async () => {
|
||||||
const userWellFormed: User = {
|
const userWellFormed: User = {
|
||||||
id: '8675309',
|
id: '8675309',
|
||||||
name: 'Test User',
|
name: 'Test User',
|
||||||
@ -39,13 +39,19 @@ 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", () => {
|
test("Renders just the user's email if no name is available", async () => {
|
||||||
const userNoName: User = {
|
const userNoName: User = {
|
||||||
id: '8675309',
|
id: '8675309',
|
||||||
email: 'kittycad.sidebar.test@example.com',
|
email: 'kittycad.sidebar.test@example.com',
|
||||||
@ -71,10 +77,12 @@ 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', () => {
|
test('Renders a menu button if no user avatar is available', async () => {
|
||||||
const userNoAvatar: User = {
|
const userNoAvatar: User = {
|
||||||
id: '8675309',
|
id: '8675309',
|
||||||
name: 'Test User',
|
name: 'Test User',
|
||||||
@ -98,10 +106,12 @@ 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'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function TestWrap({ children }: { children: React.ReactNode }) {
|
function TestWrap({ children }: { children: React.ReactNode }) {
|
||||||
|
6
src/hooks/useProjectsContext.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { ProjectsMachineContext } from 'components/ProjectsContextProvider'
|
||||||
|
import { useContext } from 'react'
|
||||||
|
|
||||||
|
export const useProjectsContext = () => {
|
||||||
|
return useContext(ProjectsMachineContext)
|
||||||
|
}
|
@ -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 { homeMachine } from 'machines/homeMachine'
|
import { projectsMachine } from 'machines/projectsMachine'
|
||||||
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 homeMachine
|
| typeof projectsMachine
|
||||||
|
|
||||||
interface UseStateMachineCommandsArgs<
|
interface UseStateMachineCommandsArgs<
|
||||||
T extends AllMachines,
|
T extends AllMachines,
|
||||||
|
@ -21,9 +21,10 @@ 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?: Program
|
ast?: Node<Program>
|
||||||
zoomToFit?: boolean
|
zoomToFit?: boolean
|
||||||
executionId?: number
|
executionId?: number
|
||||||
zoomOnRangeAndType?: {
|
zoomOnRangeAndType?: {
|
||||||
@ -33,13 +34,13 @@ interface ExecuteArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class KclManager {
|
export class KclManager {
|
||||||
private _ast: Program = {
|
private _ast: Node<Program> = {
|
||||||
body: [],
|
body: [],
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
nonCodeMeta: {
|
nonCodeMeta: {
|
||||||
nonCodeNodes: {},
|
nonCodeNodes: {},
|
||||||
start: [],
|
startNodes: [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
private _execState: ExecState = emptyExecState()
|
private _execState: ExecState = emptyExecState()
|
||||||
@ -55,7 +56,7 @@ export class KclManager {
|
|||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
|
|
||||||
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
||||||
private _astCallBack: (arg: Program) => void = () => {}
|
private _astCallBack: (arg: Node<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 = () => {}
|
||||||
@ -181,7 +182,7 @@ export class KclManager {
|
|||||||
setWasmInitFailed,
|
setWasmInitFailed,
|
||||||
}: {
|
}: {
|
||||||
setProgramMemory: (arg: ProgramMemory) => void
|
setProgramMemory: (arg: ProgramMemory) => void
|
||||||
setAst: (arg: Program) => void
|
setAst: (arg: Node<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
|
||||||
@ -205,12 +206,12 @@ export class KclManager {
|
|||||||
end: 0,
|
end: 0,
|
||||||
nonCodeMeta: {
|
nonCodeMeta: {
|
||||||
nonCodeNodes: {},
|
nonCodeNodes: {},
|
||||||
start: [],
|
startNodes: [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
safeParse(code: string): Program | null {
|
safeParse(code: string): Node<Program> | null {
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
this.lints = []
|
this.lints = []
|
||||||
this.kclErrors = []
|
this.kclErrors = []
|
||||||
@ -377,7 +378,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<CallExpression>(
|
const _node1 = getNodeFromPath<Node<CallExpression>>(
|
||||||
this.ast,
|
this.ast,
|
||||||
artifact.codeRef.pathToNode,
|
artifact.codeRef.pathToNode,
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
@ -441,7 +442,7 @@ export class KclManager {
|
|||||||
// 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: Program,
|
ast: Node<Program>,
|
||||||
execute: boolean,
|
execute: boolean,
|
||||||
optionalParams?: {
|
optionalParams?: {
|
||||||
focusPath?: Array<PathToNode>
|
focusPath?: Array<PathToNode>
|
||||||
@ -452,7 +453,7 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
): Promise<{
|
): Promise<{
|
||||||
newAst: Program
|
newAst: Node<Program>
|
||||||
selections?: Selections
|
selections?: Selections
|
||||||
}> {
|
}> {
|
||||||
const newCode = recast(ast)
|
const newCode = recast(ast)
|
||||||
@ -588,7 +589,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: Program) {
|
_isAstEmpty(ast: Node<Program>) {
|
||||||
return ast.start === 0 && ast.end === 0 && ast.body.length === 0
|
return ast.start === 0 && ast.end === 0 && ast.body.length === 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
sourceRange: [46, 71],
|
sourceRange: [46, 71],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value: [
|
paths: [
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'ToPoint',
|
||||||
tag: null,
|
tag: null,
|
||||||
@ -96,7 +96,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
on: expect.any(Object),
|
on: expect.any(Object),
|
||||||
start: expect.any(Object),
|
start: expect.any(Object),
|
||||||
type: 'Sketch',
|
type: 'Sketch',
|
||||||
value: [
|
paths: [
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'ToPoint',
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
@ -202,7 +202,7 @@ const sk2 = startSketchOn('XY')
|
|||||||
info: expect.any(Object),
|
info: expect.any(Object),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value: [
|
paths: [
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'ToPoint',
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
@ -294,7 +294,7 @@ const sk2 = startSketchOn('XY')
|
|||||||
info: expect.any(Object),
|
info: expect.any(Object),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value: [
|
paths: [
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'ToPoint',
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
|
@ -20,6 +20,8 @@ export default class CodeManager {
|
|||||||
private _hotkeys: { [key: string]: () => void } = {}
|
private _hotkeys: { [key: string]: () => void } = {}
|
||||||
private timeoutWriter: ReturnType<typeof setTimeout> | undefined = undefined
|
private timeoutWriter: ReturnType<typeof setTimeout> | undefined = undefined
|
||||||
|
|
||||||
|
public writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (isDesktop()) {
|
if (isDesktop()) {
|
||||||
this.code = ''
|
this.code = ''
|
||||||
@ -120,6 +122,7 @@ export default class CodeManager {
|
|||||||
// and file-system watchers which read, will receive empty data during
|
// and file-system watchers which read, will receive empty data during
|
||||||
// writes.
|
// writes.
|
||||||
clearTimeout(this.timeoutWriter)
|
clearTimeout(this.timeoutWriter)
|
||||||
|
this.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true
|
||||||
this.timeoutWriter = setTimeout(() => {
|
this.timeoutWriter = setTimeout(() => {
|
||||||
// Wait one event loop to give a chance for params to be set
|
// Wait one event loop to give a chance for params to be set
|
||||||
// Save the file to disk
|
// Save the file to disk
|
||||||
|
@ -58,7 +58,7 @@ const newVar = myVar + 1`
|
|||||||
`
|
`
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
// geo is three js buffer geometry and is very bloated to have in tests
|
// geo is three js buffer geometry and is very bloated to have in tests
|
||||||
const minusGeo = mem.get('mySketch')?.value?.value
|
const minusGeo = mem.get('mySketch')?.value?.paths
|
||||||
expect(minusGeo).toEqual([
|
expect(minusGeo).toEqual([
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'ToPoint',
|
||||||
@ -175,7 +175,7 @@ const newVar = myVar + 1`
|
|||||||
info: expect.any(Object),
|
info: expect.any(Object),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value: [
|
paths: [
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'ToPoint',
|
||||||
to: [1, 1],
|
to: [1, 1],
|
||||||
@ -367,7 +367,7 @@ describe('testing math operators', () => {
|
|||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
||||||
// result of `-legLen(5, min(3, 999))` should be -4
|
// result of `-legLen(5, min(3, 999))` should be -4
|
||||||
const yVal = (sketch as Sketch).value?.[0]?.to?.[1]
|
const yVal = (sketch as Sketch).paths?.[0]?.to?.[1]
|
||||||
expect(yVal).toBe(-4)
|
expect(yVal).toBe(-4)
|
||||||
})
|
})
|
||||||
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
||||||
@ -385,8 +385,8 @@ describe('testing math operators', () => {
|
|||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
||||||
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
|
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
|
||||||
expect((sketch as Sketch).value?.[1]?.from).toEqual([3, 4])
|
expect((sketch as Sketch).paths?.[1]?.from).toEqual([3, 4])
|
||||||
expect((sketch as Sketch).value?.[1]?.to).toEqual([6, 0])
|
expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0])
|
||||||
const removedUnaryExp = code.replace(
|
const removedUnaryExp = code.replace(
|
||||||
`-legLen(segLen(seg01), myVar)`,
|
`-legLen(segLen(seg01), myVar)`,
|
||||||
`legLen(segLen(seg01), myVar)`
|
`legLen(segLen(seg01), myVar)`
|
||||||
@ -398,7 +398,7 @@ describe('testing math operators', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// without the minus sign, the y value should be 8
|
// without the minus sign, the y value should be 8
|
||||||
expect((removedUnaryExpMemSketch as Sketch).value?.[1]?.to).toEqual([6, 8])
|
expect((removedUnaryExpMemSketch as Sketch).paths?.[1]?.to).toEqual([6, 8])
|
||||||
})
|
})
|
||||||
it('with nested callExpression and binaryExpression', async () => {
|
it('with nested callExpression and binaryExpression', async () => {
|
||||||
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||||
|
@ -12,6 +12,7 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
|
|||||||
import { KCLError } from 'lang/errors'
|
import { KCLError } from 'lang/errors'
|
||||||
import { Diagnostic } from '@codemirror/lint'
|
import { Diagnostic } from '@codemirror/lint'
|
||||||
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
|
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
|
||||||
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
|
||||||
export type ToolTip =
|
export type ToolTip =
|
||||||
| 'lineTo'
|
| 'lineTo'
|
||||||
@ -52,7 +53,7 @@ export async function executeAst({
|
|||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
idGenerator,
|
idGenerator,
|
||||||
}: {
|
}: {
|
||||||
ast: Program
|
ast: Node<Program>
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
useFakeExecutor?: boolean
|
useFakeExecutor?: boolean
|
||||||
programMemoryOverride?: ProgramMemory
|
programMemoryOverride?: ProgramMemory
|
||||||
|
@ -21,6 +21,7 @@ import { enginelessExecutor } from '../lib/testHelpers'
|
|||||||
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
|
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { SimplifiedArgDetails } from './std/stdTypes'
|
import { SimplifiedArgDetails } from './std/stdTypes'
|
||||||
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -109,7 +110,7 @@ describe('Testing findUniqueName', () => {
|
|||||||
{ type: 'Identifier', name: 'yo07', start: 0, end: 0 },
|
{ type: 'Identifier', name: 'yo07', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo08', start: 0, end: 0 },
|
{ type: 'Identifier', name: 'yo08', start: 0, end: 0 },
|
||||||
{ type: 'Identifier', name: 'yo09', start: 0, end: 0 },
|
{ type: 'Identifier', name: 'yo09', start: 0, end: 0 },
|
||||||
] satisfies Identifier[]),
|
] satisfies Node<Identifier>[]),
|
||||||
'yo',
|
'yo',
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
@ -123,7 +124,7 @@ describe('Testing addSketchTo', () => {
|
|||||||
body: [],
|
body: [],
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
nonCodeMeta: { nonCodeNodes: {}, start: [] },
|
nonCodeMeta: { nonCodeNodes: {}, startNodes: [] },
|
||||||
},
|
},
|
||||||
'yz'
|
'yz'
|
||||||
)
|
)
|
||||||
|