Compare commits
108 Commits
v1.0.7
...
stream-pau
Author | SHA1 | Date | |
---|---|---|---|
c6b80eec68 | |||
1b68f5dc19 | |||
a0aa4802d1 | |||
746f76ec63 | |||
8e624935c2 | |||
0a5f22c80a | |||
142db64796 | |||
59b0cdc3ac | |||
4763257dc3 | |||
b48ba7f081 | |||
64537a59b8 | |||
c24efaf2e4 | |||
2fb16ed074 | |||
82e647db3b | |||
fa2d0a69bf | |||
e372b2680e | |||
c1c1f817c9 | |||
00c0c993f2 | |||
b505c0be07 | |||
6c2d06c2c6 | |||
a4b7dd5182 | |||
0c2ca726d0 | |||
fcfecf702b | |||
347a6ef15a | |||
eed4386f76 | |||
14afcba599 | |||
faee6cbc64 | |||
0673e98fad | |||
b4eea5f842 | |||
2c9eb7f7c0 | |||
e259b2e3e8 | |||
91049204c5 | |||
0128c67aae | |||
ecc42b1e9c | |||
31811d0269 | |||
def5959836 | |||
4c7fab405b | |||
1e12e8d36b | |||
ad775891a3 | |||
efe207f4d2 | |||
01f0162991 | |||
2bbf7fad67 | |||
98549945a4 | |||
315fdc3060 | |||
c7e77e2597 | |||
a48679c014 | |||
c5e74866a9 | |||
731cb6c532 | |||
8a36a4c205 | |||
f29f2557de | |||
5f0ffb56c4 | |||
70078176b0 | |||
098fa2b5c9 | |||
2755156b84 | |||
a8b3ec660d | |||
3747c6ff0e | |||
bc1bc817ba | |||
b415e88746 | |||
9173e368a2 | |||
25928813e3 | |||
aec9cac7c7 | |||
0e82fbf7b0 | |||
f5975bbd61 | |||
765e587f6b | |||
6ccd5e22b8 | |||
c8bf82ba04 | |||
daad2039ec | |||
b567f6dfad | |||
41e85c77ac | |||
9f615b9d3e | |||
e8b5618b34 | |||
799b2d77b4 | |||
7b569f9b4f | |||
0f0c396a0c | |||
83214a88a3 | |||
dbab7876de | |||
6706695502 | |||
fa1f8d8d02 | |||
b4e59b5c56 | |||
20495383ac | |||
7f5fb83761 | |||
2ac874971e | |||
230e3132e9 | |||
bf9bb4fb22 | |||
31e7634669 | |||
84c71aa046 | |||
721b3e8cbd | |||
89309b6ccd | |||
15b163bba8 | |||
60d047ef6a | |||
f105044a47 | |||
9388e09c47 | |||
d71f2af9bd | |||
2bb372de12 | |||
0a3a8afbbd | |||
351df2f306 | |||
05a2eada9a | |||
788270d4fc | |||
6845f0c4bc | |||
563096fba4 | |||
35133c4f45 | |||
b78c6508c2 | |||
08b776134f | |||
cca544189c | |||
69754c82a2 | |||
afbee552ee | |||
b11772b27c | |||
6dc87aa4fe |
@ -4,9 +4,9 @@ set -euo pipefail
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
if [[ "$3" == "ubuntu-latest" ]]; then
|
||||
if [[ "$3" == ubuntu-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:ubuntu -- --shard=$1/$2 || true
|
||||
elif [[ "$3" == "windows-latest" ]]; then
|
||||
elif [[ "$3" == windows-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:windows -- --shard=$1/$2 || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
@ -26,9 +26,9 @@ while [[ $retry -le $max_retrys ]]; do
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
if [[ "$3" == "ubuntu-latest" ]]; then
|
||||
if [[ "$3" == ubuntu-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:ubuntu -- --last-failed || true
|
||||
elif [[ "$3" == "windows-latest" ]]; then
|
||||
elif [[ "$3" == windows-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:windows -- --last-failed || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
|
12
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -4,11 +4,11 @@ set -euo pipefail
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
if [[ "$1" == "ubuntu-latest" ]]; then
|
||||
if [[ "$1" == ubuntu-latest* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true
|
||||
elif [[ "$1" == "windows-latest" ]]; then
|
||||
elif [[ "$1" == windows-latest* ]]; then
|
||||
yarn test:playwright:electron:windows || true
|
||||
elif [[ "$1" == "macos-14" ]]; then
|
||||
elif [[ "$1" == macos-14* ]]; then
|
||||
yarn test:playwright:electron:macos || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
@ -28,11 +28,11 @@ while [[ $retry -le $max_retrys ]]; do
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
if [[ "$1" == "ubuntu-latest" ]]; then
|
||||
if [[ "$1" == ubuntu-latest* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true
|
||||
elif [[ "$1" == "windows-latest" ]]; then
|
||||
elif [[ "$1" == windows-latest* ]]; then
|
||||
yarn test:playwright:electron:windows -- --last-failed || true
|
||||
elif [[ "$1" == "macos-14" ]]; then
|
||||
elif [[ "$1" == macos-14* ]]; then
|
||||
yarn test:playwright:electron:macos -- --last-failed || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
|
6
.github/dependabot.yml
vendored
@ -8,21 +8,21 @@ updates:
|
||||
- package-ecosystem: 'npm' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: 'weekly'
|
||||
reviewers:
|
||||
- franknoirot
|
||||
- irev-dev
|
||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: 'weekly'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src/wasm-lib/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: 'weekly'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
|
13
.github/workflows/build-test-publish-apps.yml
vendored
@ -85,7 +85,7 @@ jobs:
|
||||
- name: Prepare electron-builder.yml file for updater test
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test-release-notes"' electron-builder.yml
|
||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test"' electron-builder.yml
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
@ -181,6 +181,7 @@ jobs:
|
||||
- name: Build the app (release)
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
env:
|
||||
PUBLISH_FOR_PULL_REQUEST: true
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
@ -360,17 +361,17 @@ jobs:
|
||||
run: "ls -R out"
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
uses: 'google-github-actions/auth@v2.1.6'
|
||||
uses: 'google-github-actions/auth@v2.1.7'
|
||||
with:
|
||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||
|
||||
- name: Set up Google Cloud SDK
|
||||
uses: google-github-actions/setup-gcloud@v2.1.0
|
||||
uses: google-github-actions/setup-gcloud@v2.1.2
|
||||
with:
|
||||
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
|
||||
|
||||
- name: Upload release files to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.1
|
||||
with:
|
||||
path: out
|
||||
glob: 'Zoo*'
|
||||
@ -378,7 +379,7 @@ jobs:
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload update endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.1
|
||||
with:
|
||||
path: out
|
||||
glob: 'latest*'
|
||||
@ -386,7 +387,7 @@ jobs:
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload download endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.1
|
||||
with:
|
||||
path: last_download.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
2
.github/workflows/cargo-check.yml
vendored
@ -37,4 +37,4 @@ jobs:
|
||||
# We specifically want to test the disable-println feature
|
||||
# Since it is not enabled by default, we need to specify it
|
||||
# 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
|
||||
|
4
.github/workflows/cargo-test.yml
vendored
@ -5,6 +5,8 @@ on:
|
||||
paths:
|
||||
- 'src/wasm-lib/**.rs'
|
||||
- 'src/wasm-lib/**.hbs'
|
||||
- 'src/wasm-lib/**.gen'
|
||||
- 'src/wasm-lib/**.snap'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
@ -15,6 +17,8 @@ on:
|
||||
paths:
|
||||
- 'src/wasm-lib/**.rs'
|
||||
- 'src/wasm-lib/**.hbs'
|
||||
- 'src/wasm-lib/**.gen'
|
||||
- 'src/wasm-lib/**.snap'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
|
6
.github/workflows/e2e-tests.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores]
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
runs-on: ${{ matrix.os }}
|
||||
@ -227,7 +227,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-14]
|
||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large]
|
||||
timeout-minutes: 60
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: check-rust-changes
|
||||
@ -287,7 +287,7 @@ jobs:
|
||||
brew install gnu-sed
|
||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Install vector
|
||||
if: ${{ !startsWith(matrix.os, 'windows') }}
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
|
25
.github/workflows/static-analysis.yml
vendored
@ -81,6 +81,31 @@ jobs:
|
||||
- name: Run codespell
|
||||
run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration.
|
||||
|
||||
yarn-unit-test-kcl-samples:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:bg
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
|
||||
- name: Install Chromium Browser
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn playwright install chromium --with-deps
|
||||
|
||||
- name: run unit tests for kcl samples
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn test:unit:kcl-samples
|
||||
env:
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
|
||||
yarn-unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
2
Makefile
@ -19,7 +19,7 @@ $(XSTATE_TYPEGENS): $(TS_SRC)
|
||||
yarn xstate typegen 'src/**/*.ts?(x)'
|
||||
|
||||
public/wasm_lib_bg.wasm: $(WASM_LIB_FILES)
|
||||
yarn build:wasm-dev
|
||||
yarn build:wasm
|
||||
|
||||
node_modules: package.json yarn.lock
|
||||
yarn install
|
||||
|
@ -110,7 +110,7 @@ Which commands from setup are one off vs need to be run every time?
|
||||
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
||||
```bash
|
||||
yarn install
|
||||
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
|
||||
yarn build:wasm
|
||||
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
||||
```
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "angleToMatchLengthX"
|
||||
excerpt: "Compute the angle (in degrees) in o"
|
||||
excerpt: "Returns the angle to match the given length for x."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Compute the angle (in degrees) in o
|
||||
Returns the angle to match the given length for x.
|
||||
|
||||
|
||||
|
||||
|
@ -84,9 +84,13 @@ layout: manual
|
||||
* [`rem`](kcl/rem)
|
||||
* [`revolve`](kcl/revolve)
|
||||
* [`segAng`](kcl/segAng)
|
||||
* [`segEnd`](kcl/segEnd)
|
||||
* [`segEndX`](kcl/segEndX)
|
||||
* [`segEndY`](kcl/segEndY)
|
||||
* [`segLen`](kcl/segLen)
|
||||
* [`segStart`](kcl/segStart)
|
||||
* [`segStartX`](kcl/segStartX)
|
||||
* [`segStartY`](kcl/segStartY)
|
||||
* [`shell`](kcl/shell)
|
||||
* [`sin`](kcl/sin)
|
||||
* [`sqrt`](kcl/sqrt)
|
||||
|
59
docs/kcl/modules.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "KCL Modules"
|
||||
excerpt: "Documentation of modules for the KCL language for the Zoo Modeling App."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
`KCL` allows splitting code up into multiple files. Each file is somewhat
|
||||
isolated from other files as a separate module.
|
||||
|
||||
When you define a function, you can use `export` before it to make it available
|
||||
to other modules.
|
||||
|
||||
```
|
||||
// util.kcl
|
||||
export fn increment = (x) => {
|
||||
return x + 1
|
||||
}
|
||||
```
|
||||
|
||||
Other files in the project can now import functions that have been exported.
|
||||
This makes them available to use in another file.
|
||||
|
||||
```
|
||||
// main.kcl
|
||||
import increment from "util.kcl"
|
||||
|
||||
answer = increment(41)
|
||||
```
|
||||
|
||||
Imported files _must_ be in the same project so that units are uniform across
|
||||
modules. This means that it must be in the same directory.
|
||||
|
||||
Import statements must be at the top-level of a file. It is not allowed to have
|
||||
an `import` statement inside a function or in the body of an if-else.
|
||||
|
||||
Multiple functions can be exported in a file.
|
||||
|
||||
```
|
||||
// util.kcl
|
||||
export fn increment = (x) => {
|
||||
return x + 1
|
||||
}
|
||||
|
||||
export fn decrement = (x) => {
|
||||
return x - 1
|
||||
}
|
||||
```
|
||||
|
||||
When importing, you can import multiple functions at once.
|
||||
|
||||
```
|
||||
import increment, decrement from "util.kcl"
|
||||
```
|
||||
|
||||
Imported symbols can be renamed for convenience or to avoid name collisions.
|
||||
|
||||
```
|
||||
import increment as inc, decrement as dec from "util.kcl"
|
||||
```
|
53
docs/kcl/segEnd.md
Normal file
56
docs/kcl/segStart.md
Normal file
43
docs/kcl/segStartX.md
Normal file
44
docs/kcl/segStartY.md
Normal file
48621
docs/kcl/std.json
@ -23,11 +23,11 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -43,10 +43,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `left` |[`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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -83,12 +83,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | 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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -104,11 +104,11 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -124,12 +124,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | 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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -145,13 +145,13 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
|
||||
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | 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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -23,12 +23,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ImportStatement`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
|
||||
| `path` |`string`| | No |
|
||||
| `raw_path` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -44,10 +44,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)| | 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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -84,10 +84,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | 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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -16,6 +16,6 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | 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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ArrayRangeExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | 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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `None`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -15,10 +15,10 @@ layout: manual
|
||||
|
||||
| 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 |
|
||||
| `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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -15,9 +15,9 @@ layout: manual
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
16
docs/kcl/types/KclNone.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "KclNone"
|
||||
excerpt: "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application)."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -23,8 +23,110 @@ Any KCL value.
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `UserVal`| | No |
|
||||
| `value` |``| | No |
|
||||
| `type` |enum: `Uuid`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Bool`| | No |
|
||||
| `value` |`boolean`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Number`| | No |
|
||||
| `value` |`number`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Int`| | No |
|
||||
| `value` |`integer`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `String`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Array`| | No |
|
||||
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Object`| | No |
|
||||
| `value` |`object`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
@ -59,10 +161,10 @@ Any KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
||||
|
||||
----
|
||||
@ -111,6 +213,38 @@ A face.
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Sketch`](/docs/kcl/types/Sketch)| | No |
|
||||
| `value` |[`Sketch`](/docs/kcl/types/Sketch)| Any KCL value. | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Sketches`| | No |
|
||||
| `value` |`[` [`Sketch`](/docs/kcl/types/Sketch) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
An solid is a collection of extrude surfaces.
|
||||
|
||||
@ -190,6 +324,23 @@ Data for an imported geometry.
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No |
|
||||
| `value` |[`KclNone`](/docs/kcl/types/KclNone)| Any KCL value. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -23,10 +23,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -23,12 +23,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | 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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -44,10 +44,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
|
||||
|
||||
|
@ -15,9 +15,9 @@ layout: manual
|
||||
|
||||
| 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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `key` |[`Identifier`](/docs/kcl/types/Identifier)| | 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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -16,10 +16,10 @@ A KCL program top level, or function body.
|
||||
|
||||
| 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 |
|
||||
| `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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
|
||||
| `currentEnv` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `currentEnv` |`integer`| | No |
|
||||
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |
|
||||
|
||||
|
||||
|
@ -15,10 +15,10 @@ layout: manual
|
||||
|
||||
| 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 |
|
||||
| `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 |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -67,15 +67,15 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
}
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
} else {
|
||||
await page.waitForTimeout(500)
|
||||
}
|
||||
@ -84,9 +84,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
||||
|> line([-${commonPoints.num2}, 0], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
}
|
||||
|
||||
// deselect line tool
|
||||
@ -142,9 +142,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %, $seg01)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
||||
|> angledLine([180, segLen(seg01)], %)`)
|
||||
|> xLine(${commonPoints.num1}, %, $seg01)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(-segLen(seg01), %)`)
|
||||
}
|
||||
|
||||
test.describe('Basic sketch', () => {
|
||||
|
@ -632,16 +632,18 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// this test might be brittle as we add and remove functions
|
||||
// but should also be easy to update.
|
||||
// tests clicking on an option, selection the first option
|
||||
// and arrowing down to an option
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('sketch001 = start')
|
||||
|
||||
// expect there to be six auto complete options
|
||||
await expect(page.locator('.cm-completionLabel')).toHaveCount(8)
|
||||
// expect there to be some auto complete options
|
||||
// exact number depends on the KCL stdlib, so let's just check it's > 0 for now.
|
||||
await expect(async () => {
|
||||
const children = await page.locator('.cm-completionLabel').count()
|
||||
expect(children).toBeGreaterThan(0)
|
||||
}).toPass()
|
||||
// this makes sure we can accept a completion with click
|
||||
await page.getByText('startSketchOn').click()
|
||||
await page.keyboard.type("'XZ'")
|
||||
@ -692,6 +694,9 @@ test.describe('Editor tests', () => {
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
|
||||
// expect there to be no KCL errors
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('with tab to accept the completion', async ({ page }) => {
|
||||
@ -985,7 +990,7 @@ test.describe('Editor tests', () => {
|
||||
|> extrude(5, %)`)
|
||||
})
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
`Can use the import stdlib function on a local OBJ file`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
|
@ -26,10 +26,6 @@ test.describe('integrations tests', () => {
|
||||
'Creating a new file or switching file while in sketchMode should exit sketchMode',
|
||||
{ tag: '@electron' },
|
||||
async ({ tronApp, homePage, scene, editor, toolbar }) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'windows times out will waiting for the execution indicator?'
|
||||
)
|
||||
await tronApp.initialise({
|
||||
fixtures: { homePage, scene, editor, toolbar },
|
||||
folderSetupFn: async (dir) => {
|
||||
@ -55,7 +51,6 @@ test.describe('integrations tests', () => {
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.openProject('test-sample')
|
||||
// windows times out here, hence the skip above
|
||||
await scene.waitForExecutionDone()
|
||||
})
|
||||
await test.step('enter sketch mode', async () => {
|
||||
@ -71,10 +66,13 @@ test.describe('integrations tests', () => {
|
||||
await toolbar.editSketch()
|
||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||
})
|
||||
|
||||
const fileName = 'Untitled.kcl'
|
||||
await test.step('check sketch mode is exited when creating new file', async () => {
|
||||
await toolbar.fileTreeBtn.click()
|
||||
await toolbar.expectFileTreeState(['main.kcl'])
|
||||
await toolbar.createFile({ wait: true })
|
||||
|
||||
await toolbar.createFile({ fileName, waitForToastToDisappear: true })
|
||||
|
||||
// check we're out of sketch mode
|
||||
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||
@ -93,10 +91,10 @@ test.describe('integrations tests', () => {
|
||||
})
|
||||
await toolbar.editSketch()
|
||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||
await toolbar.expectFileTreeState(['main.kcl', 'Untitled.kcl'])
|
||||
await toolbar.expectFileTreeState(['main.kcl', fileName])
|
||||
})
|
||||
await test.step('check sketch mode is exited when opening a different file', async () => {
|
||||
await toolbar.openFile('untitled.kcl', { wait: false })
|
||||
await toolbar.openFile(fileName, { wait: false })
|
||||
|
||||
// check we're out of sketch mode
|
||||
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||
@ -109,7 +107,7 @@ test.describe('when using the file tree to', () => {
|
||||
const fromFile = 'main.kcl'
|
||||
const toFile = 'hello.kcl'
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
@ -157,7 +155,7 @@ test.describe('when using the file tree to', () => {
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
`create many new untitled files they increment their names`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
@ -193,61 +191,74 @@ test.describe('when using the file tree to', () => {
|
||||
test(
|
||||
'create a new file with the same name as an existing file cancels the operation',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
async (
|
||||
{ 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 {
|
||||
openKclCodePanel,
|
||||
openFilePanel,
|
||||
pasteCodeInEditor,
|
||||
createNewFileAndSelect,
|
||||
renameFile,
|
||||
selectFile,
|
||||
editorTextMatches,
|
||||
waitForPageLoad,
|
||||
} = await getUtils(tronApp.page, _test)
|
||||
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
await test.step(`Setup: Open project and navigate to ${secondFile}`, async () => {
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: projectName,
|
||||
fileCount: 2,
|
||||
folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.openProject(projectName)
|
||||
await waitForPageLoad()
|
||||
await openFilePanel()
|
||||
await selectFile(secondFile)
|
||||
})
|
||||
|
||||
await createProject({ name: 'project-000', page: tronApp.page })
|
||||
await openKclCodePanel()
|
||||
await openFilePanel()
|
||||
// File the main.kcl with contents
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
await test.step(`Attempt to rename ${secondFile} to ${mainFile}`, async () => {
|
||||
await renameFile(secondFile, mainFile)
|
||||
})
|
||||
|
||||
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
|
||||
await tronApp.page.waitForTimeout(2000)
|
||||
|
||||
const kcl1 = 'main.kcl'
|
||||
const kcl2 = '2.kcl'
|
||||
await createNewFileAndSelect(kcl2)
|
||||
const kclCylinder = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCylinder)
|
||||
|
||||
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
|
||||
await tronApp.page.waitForTimeout(2000)
|
||||
|
||||
await renameFile(kcl2, kcl1)
|
||||
|
||||
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {
|
||||
await selectFile(kcl1)
|
||||
await test.step(`Postcondition: ${mainFile} still has the original content`, async () => {
|
||||
await selectFile(mainFile)
|
||||
await editorTextMatches(kclCube)
|
||||
})
|
||||
await tronApp.page.waitForTimeout(500)
|
||||
|
||||
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
|
||||
await selectFile(kcl2)
|
||||
await test.step(`Postcondition: ${secondFile} still exists with the original content`, async () => {
|
||||
await selectFile(secondFile)
|
||||
await editorTextMatches(kclCylinder)
|
||||
})
|
||||
|
||||
await tronApp?.close?.()
|
||||
await tronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
@ -273,11 +284,11 @@ test.describe('when using the file tree to', () => {
|
||||
)
|
||||
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('')
|
||||
})
|
||||
|
||||
@ -285,7 +296,7 @@ test.describe('when using the file tree to', () => {
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
'loading small file, then large, then back to small',
|
||||
{
|
||||
tag: '@electron',
|
||||
|
@ -1,6 +1,11 @@
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
import { sansWhitespace } from '../test-utils'
|
||||
import {
|
||||
closePane,
|
||||
checkIfPaneIsOpen,
|
||||
openPane,
|
||||
sansWhitespace,
|
||||
} from '../test-utils'
|
||||
|
||||
interface EditorState {
|
||||
activeLines: Array<string>
|
||||
@ -11,6 +16,7 @@ interface EditorState {
|
||||
export class EditorFixture {
|
||||
public page: Page
|
||||
|
||||
private paneButtonTestId = 'code-pane-button'
|
||||
private diagnosticsTooltip!: Locator
|
||||
private diagnosticsGutterIcon!: Locator
|
||||
private codeContent!: Locator
|
||||
@ -31,19 +37,32 @@ export class EditorFixture {
|
||||
|
||||
private _expectEditorToContain =
|
||||
(not = false) =>
|
||||
(
|
||||
async (
|
||||
code: string,
|
||||
{
|
||||
shouldNormalise = false,
|
||||
timeout = 5_000,
|
||||
}: { shouldNormalise?: boolean; timeout?: number } = {}
|
||||
) => {
|
||||
const wasPaneOpen = await this.checkIfPaneIsOpen()
|
||||
if (!wasPaneOpen) {
|
||||
await this.openPane()
|
||||
}
|
||||
const resetPane = async () => {
|
||||
if (!wasPaneOpen) {
|
||||
await this.closePane()
|
||||
}
|
||||
}
|
||||
if (!shouldNormalise) {
|
||||
const expectStart = expect(this.codeContent)
|
||||
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 expectStart = expect.poll(
|
||||
@ -56,9 +75,13 @@ export class EditorFixture {
|
||||
}
|
||||
)
|
||||
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 = {
|
||||
toContain: this._expectEditorToContain(),
|
||||
@ -115,4 +138,13 @@ export class EditorFixture {
|
||||
code = code.replace(findCode, replaceCode)
|
||||
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 context: BrowserContext
|
||||
public readonly testInfo: TestInfo
|
||||
public readonly viewPortSize = { width: 1000, height: 500 }
|
||||
|
||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||
this.page = page
|
||||
@ -36,7 +37,7 @@ export class AuthenticatedApp {
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
}, code)
|
||||
|
||||
await this.page.setViewportSize({ width: 1000, height: 500 })
|
||||
await this.page.setViewportSize(this.viewPortSize)
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
}
|
||||
|
@ -10,7 +10,13 @@ import {
|
||||
} from '../test-utils'
|
||||
|
||||
type mouseParams = {
|
||||
pixelDiff: number
|
||||
pixelDiff?: number
|
||||
}
|
||||
type mouseDragToParams = mouseParams & {
|
||||
fromPoint: { x: number; y: number }
|
||||
}
|
||||
type mouseDragFromParams = mouseParams & {
|
||||
toPoint: { x: number; y: number }
|
||||
}
|
||||
|
||||
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 {
|
||||
public page: Page
|
||||
|
||||
@ -55,7 +68,7 @@ export class SceneFixture {
|
||||
x: number,
|
||||
y: number,
|
||||
{ steps }: { steps: number } = { steps: 20 }
|
||||
) =>
|
||||
): [ClickHandler, MoveHandler] =>
|
||||
[
|
||||
(clickParams?: mouseParams) => {
|
||||
if (clickParams?.pixelDiff) {
|
||||
@ -78,6 +91,47 @@ export class SceneFixture {
|
||||
return this.page.mouse.move(x, y, { steps })
|
||||
},
|
||||
] 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.
|
||||
*
|
||||
@ -141,7 +195,7 @@ export class SceneFixture {
|
||||
}
|
||||
|
||||
waitForExecutionDone = async () => {
|
||||
await expect(this.exeIndicator).toBeVisible()
|
||||
await expect(this.exeIndicator).toBeVisible({ timeout: 30000 })
|
||||
}
|
||||
|
||||
expectPixelColor = async (
|
||||
|
@ -7,6 +7,7 @@ export class ToolbarFixture {
|
||||
|
||||
extrudeButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
lineBtn!: Locator
|
||||
rectangleBtn!: Locator
|
||||
exitSketchBtn!: Locator
|
||||
editSketchBtn!: Locator
|
||||
@ -15,6 +16,7 @@ export class ToolbarFixture {
|
||||
fileCreateToast!: Locator
|
||||
filePane!: Locator
|
||||
exeIndicator!: Locator
|
||||
treeInputField!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
@ -24,11 +26,13 @@ export class ToolbarFixture {
|
||||
this.page = page
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.lineBtn = page.getByTestId('line')
|
||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
||||
this.editSketchBtn = page.getByText('Edit Sketch')
|
||||
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
||||
this.createFileBtn = page.getByTestId('create-file-button')
|
||||
this.treeInputField = page.getByTestId('tree-input-field')
|
||||
|
||||
this.filePane = page.locator('#files-pane')
|
||||
this.fileCreateToast = page.getByText('Successfully created')
|
||||
@ -57,10 +61,15 @@ export class ToolbarFixture {
|
||||
expectFileTreeState = async (expected: string[]) => {
|
||||
await expect.poll(this._serialiseFileTree).toEqual(expected)
|
||||
}
|
||||
createFile = async ({ wait }: { wait: boolean } = { wait: false }) => {
|
||||
createFile = async (args: {
|
||||
fileName: string
|
||||
waitForToastToDisappear: boolean
|
||||
}) => {
|
||||
await this.createFileBtn.click()
|
||||
await this.treeInputField.fill(args.fileName)
|
||||
await this.treeInputField.press('Enter')
|
||||
await expect(this.fileCreateToast).toBeVisible()
|
||||
if (wait) {
|
||||
if (args.waitForToastToDisappear) {
|
||||
await this.fileCreateToast.waitFor({ state: 'detached' })
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export const isErrorWhitelisted = (exception: Error) => {
|
||||
{
|
||||
name: '"{"kind"',
|
||||
message:
|
||||
'"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"',
|
||||
'"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"',
|
||||
stack: '',
|
||||
foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
|
||||
project: 'Google Chrome',
|
||||
@ -156,8 +156,8 @@ export const isErrorWhitelisted = (exception: Error) => {
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message:
|
||||
'{"kind":"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}',
|
||||
stack: `Unhandled Promise Rejection: {"kind":"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: \`JsValue(undefined)\`"}
|
||||
'{"kind":"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}',
|
||||
stack: `Unhandled Promise Rejection: {"kind":"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: \`JsValue(undefined)\`"}
|
||||
at unknown (http://localhost:3000/src/lang/std/engineConnection.ts:1245:26)`,
|
||||
foundInSpec:
|
||||
'e2e/playwright/onboarding-tests.spec.ts Click through each onboarding step',
|
||||
@ -253,7 +253,7 @@ export const isErrorWhitelisted = (exception: Error) => {
|
||||
{
|
||||
name: '{"kind"',
|
||||
stack: ``,
|
||||
message: `engine","sourceRanges":[[0,0]],"msg":"Failed to wait for promise from engine: JsValue(\\"Force interrupt, executionIsStale, new AST requested\\")"}`,
|
||||
message: `engine","sourceRanges":[[0,0,0]],"msg":"Failed to wait for promise from engine: JsValue(\\"Force interrupt, executionIsStale, new AST requested\\")"}`,
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
|
||||
},
|
||||
|
@ -451,3 +451,103 @@ sketch002 = startSketchOn(extrude001, seg03)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(`Verify axis, origin, and horizontal 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: `xLine(${xAxisSloppy.kcl[0]}, %)`,
|
||||
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
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -854,7 +854,7 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -1490,13 +1490,13 @@ test(
|
||||
'function_sketch.kcl',
|
||||
'function_sketch_with_position.kcl',
|
||||
'global-tags.kcl',
|
||||
'helix_ccw.kcl',
|
||||
'helix_defaults.kcl',
|
||||
'helix_defaults_negative_extrude.kcl',
|
||||
'helix_with_length.kcl',
|
||||
'i_shape.kcl',
|
||||
'kittycad_svg.kcl',
|
||||
'lego.kcl',
|
||||
'lsystem.kcl',
|
||||
'math.kcl',
|
||||
'member_expression_sketch.kcl',
|
||||
'mike_stress_test.kcl',
|
||||
@ -1669,7 +1669,8 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
// Flaky
|
||||
test.fixme(
|
||||
'Original project name persist after onboarding',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
|
@ -115,7 +115,7 @@ test.describe('Sketch tests', () => {
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> xLine(12.73, %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)`
|
||||
)
|
||||
})
|
||||
@ -156,7 +156,7 @@ test.describe('Sketch tests', () => {
|
||||
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
|> line([-12.34, 12.34], %)
|
||||
|> yLine(12.34, %)
|
||||
|
||||
`)
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
@ -202,35 +202,19 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const viewport = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewport)
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.closeDebugPanel()
|
||||
const center = {
|
||||
x: viewport.width / 2,
|
||||
y: viewport.height / 2,
|
||||
}
|
||||
const modelAreaSize = await u.getModelViewAreaSize()
|
||||
|
||||
// If we have the code pane open, we should see the code.
|
||||
if (openPanes.includes('code')) {
|
||||
@ -244,7 +228,7 @@ test.describe('Sketch tests', () => {
|
||||
await expect(u.codeLocator).not.toBeVisible()
|
||||
}
|
||||
|
||||
const startPX = [665, 458]
|
||||
const startPX = [center.x + 65, 458]
|
||||
|
||||
const dragPX = 30
|
||||
let prevContent = ''
|
||||
@ -255,7 +239,7 @@ test.describe('Sketch tests', () => {
|
||||
// Wait for the render.
|
||||
await page.waitForTimeout(1000)
|
||||
// Select the sketch
|
||||
await page.mouse.click(700, 370)
|
||||
await page.mouse.click(center.x + 100, 370)
|
||||
}
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
@ -266,45 +250,74 @@ test.describe('Sketch tests', () => {
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 - modelAreaSize.w },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(1000)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const step5 = { steps: 5 }
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
test.step('drag startProfileAt handle', async () => {
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
})
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
|
||||
// drag line handle
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.mouse.move(lineEnd.x - 5, lineEnd.y)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
test.step('drag line handle', async () => {
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.mouse.move(lineEnd.x - 5, lineEnd.y)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
})
|
||||
|
||||
// drag tangentialArcTo handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(tangentEnd.x + dragPX, tangentEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
}
|
||||
test.step('drag tangentialArcTo handle', async () => {
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(
|
||||
tangentEnd.x + dragPX,
|
||||
tangentEnd.y - dragPX,
|
||||
step5
|
||||
)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
}
|
||||
})
|
||||
|
||||
// Open the code pane
|
||||
await u.openKclCodePanel()
|
||||
@ -580,7 +593,7 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [665, 458]
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
|
||||
const dragPX = 30
|
||||
|
||||
@ -596,7 +609,7 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
// drag startProfileAt handle
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||
@ -637,8 +650,8 @@ test.describe('Sketch tests', () => {
|
||||
|> revolve({ axis: "X" }, %)`)
|
||||
})
|
||||
test('Can add multiple sketches', async ({ page }) => {
|
||||
test.skip(process.platform === 'darwin', 'Can add multiple sketches')
|
||||
const u = await getUtils(page)
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
@ -646,7 +659,7 @@ test.describe('Sketch tests', () => {
|
||||
await u.openDebugPanel()
|
||||
|
||||
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||
const { toSU, click00r } = getMovementUtils({ center, page })
|
||||
const { toSU, toU, click00r } = getMovementUtils({ center, page })
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
@ -662,28 +675,32 @@ test.describe('Sketch tests', () => {
|
||||
200
|
||||
)
|
||||
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
|
||||
let codeStr = "sketch001 = startSketchOn('XY')"
|
||||
|
||||
await page.mouse.click(center.x, viewportSize.height * 0.55)
|
||||
await page.mouse.click(center.x - 50, viewportSize.height * 0.55)
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
await u.closeDebugPanel()
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
await click00r(0, 0)
|
||||
codeStr += ` |> startProfileAt(${toSU([0, 0])}, %)`
|
||||
const { click00r } = await getMovementUtils({ center, page })
|
||||
|
||||
let coord = await click00r(0, 0)
|
||||
codeStr += ` |> startProfileAt(${coord.kcl}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(50, 0)
|
||||
await page.waitForTimeout(100)
|
||||
codeStr += ` |> line(${toSU([50, 0])}, %)`
|
||||
codeStr += ` |> xLine(${toU(50, 0)[0]}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(0, 50)
|
||||
codeStr += ` |> line(${toSU([0, 50])}, %)`
|
||||
codeStr += ` |> yLine(${toU(0, 50)[1]}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(-50, 0)
|
||||
codeStr += ` |> line(${toSU([-50, 0])}, %)`
|
||||
codeStr += ` |> xLine(${toU(-50, 0)[0]}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
// exit the sketch, reset relative clicker
|
||||
@ -699,26 +716,29 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
// when exiting the sketch above the camera is still looking down at XY,
|
||||
// so selecting the plane again is a bit easier.
|
||||
await page.mouse.click(center.x + 200, center.y + 100)
|
||||
await page.mouse.move(center.x - 100, center.y + 50, { steps: 5 })
|
||||
await page.mouse.click(center.x - 100, center.y + 50)
|
||||
await page.waitForTimeout(600) // TODO detect animation ending, or disable animation
|
||||
codeStr += "sketch002 = startSketchOn('XY')"
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await click00r(30, 0)
|
||||
codeStr += ` |> startProfileAt([2.03, 0], %)`
|
||||
coord = await click00r(30, 0)
|
||||
codeStr += ` |> startProfileAt(${coord.kcl}, %)`
|
||||
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)
|
||||
codeStr += ` |> line([2.04, 0], %)`
|
||||
codeStr += ` |> xLine(2.04, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(0, 30)
|
||||
codeStr += ` |> line([0, -2.03], %)`
|
||||
codeStr += ` |> yLine(-2.03, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(-30, 0)
|
||||
codeStr += ` |> line([-2.04, 0], %)`
|
||||
codeStr += ` |> xLine(-2.04, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(undefined, undefined)
|
||||
@ -742,8 +762,8 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
const code = `sketch001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|
||||
|> line([${roundOff(scale * 139.19)}, 0], %)
|
||||
|> line([0, -${roundOff(scale * 139.2)}], %)
|
||||
|> xLine(${roundOff(scale * 139.19)}, %)
|
||||
|> yLine(-${roundOff(scale * 139.2)}, %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
|
||||
@ -762,20 +782,21 @@ test.describe('Sketch tests', () => {
|
||||
await u.updateCamPosition(camPos)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
await page.mouse.move(0, 0)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.move(700, 200, { steps: 10 })
|
||||
await page.mouse.click(700, 200, { delay: 200 })
|
||||
await page.mouse.move(center.x + 100, 200, { steps: 10 })
|
||||
await page.mouse.click(center.x + 100, 200, { delay: 200 })
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('-XZ')`
|
||||
)
|
||||
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
const pointA = [700, 200]
|
||||
const pointB = [900, 200]
|
||||
const pointC = [900, 400]
|
||||
const pointA = [center.x + 100, 200]
|
||||
const pointB = [center.x + 300, 200]
|
||||
const pointC = [center.x + 300, 400]
|
||||
|
||||
// draw three lines
|
||||
await page.waitForTimeout(500)
|
||||
@ -912,7 +933,9 @@ extrude001 = extrude(5, sketch001)
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
await page.mouse.click(622, 355)
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
|
||||
await page.mouse.click(center.x + 22, 355)
|
||||
|
||||
await page.waitForTimeout(800)
|
||||
await page.getByText(`END')`).click()
|
||||
|
@ -462,7 +462,7 @@ test(
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
code += `
|
||||
|> line([7.25, 0], %)`
|
||||
|> xLine(7.25, %)`
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
|
||||
await page
|
||||
@ -647,7 +647,7 @@ test.describe(
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
code += `
|
||||
|> line([7.25, 0], %)`
|
||||
|> xLine(7.25, %)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
@ -752,7 +752,7 @@ test.describe(
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
code += `
|
||||
|> line([184.3, 0], %)`
|
||||
|> xLine(184.3, %)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
@ -1031,7 +1031,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('theme persists', async ({ page, context }) => {
|
||||
test.fixme('theme persists', async ({ page, context }) => {
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
|
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: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 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 |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
@ -141,7 +141,7 @@ test.describe('Test network and connection issues', () => {
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
@ -207,7 +207,7 @@ test.describe('Test network and connection issues', () => {
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
|> line([12.34, 0], %)
|
||||
|> xLine(12.34, %)
|
||||
|> line([-12.34, 12.34], %)
|
||||
|
||||
`)
|
||||
@ -217,9 +217,9 @@ test.describe('Test network and connection issues', () => {
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
|> line([12.34, 0], %)
|
||||
|> xLine(12.34, %)
|
||||
|> line([-12.34, 12.34], %)
|
||||
|> line([-12.34, 0], %)
|
||||
|> xLine(-12.34, %)
|
||||
|
||||
`)
|
||||
|
||||
|
@ -8,6 +8,21 @@ import {
|
||||
Locator,
|
||||
test,
|
||||
} from '@playwright/test'
|
||||
import {
|
||||
OrthographicCamera,
|
||||
Mesh,
|
||||
Scene,
|
||||
Raycaster,
|
||||
PlaneGeometry,
|
||||
MeshBasicMaterial,
|
||||
DoubleSide,
|
||||
Vector2,
|
||||
Vector3,
|
||||
} from 'three'
|
||||
import {
|
||||
RAYCASTABLE_PLANE,
|
||||
INTERSECTION_PLANE_LAYER,
|
||||
} from 'clientSideScene/constants'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import fsp from 'fs/promises'
|
||||
import fsSync from 'fs'
|
||||
@ -45,7 +60,9 @@ export const commonPoints = {
|
||||
startAt: '[7.19, -9.7]',
|
||||
num1: 7.25,
|
||||
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
|
||||
* in dark mode in the default camera position
|
||||
@ -118,15 +135,32 @@ async function waitForDefaultPlanesToBeVisible(page: Page) {
|
||||
)
|
||||
}
|
||||
|
||||
async function openPane(page: Page, testId: string) {
|
||||
const locator = page.getByTestId(testId)
|
||||
await expect(locator).toBeVisible()
|
||||
const isOpen = (await locator?.getAttribute('aria-pressed')) === 'true'
|
||||
export async function checkIfPaneIsOpen(page: Page, testId: string) {
|
||||
const paneButtonLocator = page.getByTestId(testId)
|
||||
await expect(paneButtonLocator).toBeVisible()
|
||||
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) {
|
||||
await locator.click()
|
||||
await expect(locator).toHaveAttribute('aria-pressed', 'true')
|
||||
await paneButtonLocator.click()
|
||||
}
|
||||
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) {
|
||||
@ -238,55 +272,141 @@ export const circleMove = async (
|
||||
}
|
||||
}
|
||||
|
||||
export const getMovementUtils = (opts: any) => {
|
||||
// The way we truncate is kinda odd apparently, so we need this function
|
||||
// "[k]itty[c]ad round"
|
||||
const kcRound = (n: number) => Math.trunc(n * 100) / 100
|
||||
export function rollingRound(n: number, digitsAfterDecimal: number) {
|
||||
const s = String(n).split('.')
|
||||
|
||||
// To translate between screen and engine ("[U]nit") coordinates
|
||||
// NOTE: these pretty much can't be perfect because of screen scaling.
|
||||
// Handle on a case-by-case.
|
||||
const toU = (x: number, y: number) => [
|
||||
kcRound(x * 0.0678),
|
||||
kcRound(-y * 0.0678), // Y is inverted in our coordinate system
|
||||
]
|
||||
// There are no decimals, just return the number.
|
||||
if (s.length === 1) return n
|
||||
|
||||
// Turn the array into a string with specific formatting
|
||||
const fromUToString = (xy: number[]) => `[${xy[0]}, ${xy[1]}]`
|
||||
// Find the closest 9. We don't care about anything beyond that.
|
||||
const nineIndex = s[1].indexOf('9')
|
||||
|
||||
// Combine because used often
|
||||
const toSU = (xy: number[]) => fromUToString(toU(xy[0], xy[1]))
|
||||
const fractStr = nineIndex > 0 ? s[1].slice(0, nineIndex + 1) : s[1]
|
||||
|
||||
let fract = Number(fractStr) / 10 ** fractStr.length
|
||||
|
||||
for (let i = fractStr.length - 1; i >= 0; i -= 1) {
|
||||
if (i === digitsAfterDecimal) break
|
||||
fract = Math.round(fract * 10 ** i) / 10 ** i
|
||||
}
|
||||
|
||||
return (Number(s[0]) + fract).toFixed(digitsAfterDecimal)
|
||||
}
|
||||
|
||||
export const getMovementUtils = async (opts: any) => {
|
||||
const sceneInfra = await opts.page.evaluate(() => window.sceneInfra)
|
||||
|
||||
// Various data for raycasting into the scene to get our XY.
|
||||
const hundredM = 100_0000
|
||||
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
|
||||
const planeMaterial = new MeshBasicMaterial({
|
||||
color: 0xff0000,
|
||||
side: DoubleSide,
|
||||
transparent: true,
|
||||
opacity: 0.5,
|
||||
})
|
||||
const scene = new Scene()
|
||||
const intersectionPlane = new Mesh(planeGeometry, planeMaterial)
|
||||
intersectionPlane.userData = { type: RAYCASTABLE_PLANE }
|
||||
intersectionPlane.name = RAYCASTABLE_PLANE
|
||||
intersectionPlane.layers.set(INTERSECTION_PLANE_LAYER)
|
||||
scene.add(intersectionPlane)
|
||||
const planeRaycaster = new Raycaster()
|
||||
planeRaycaster.far = Infinity
|
||||
planeRaycaster.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||
|
||||
const kcRound = (n: number) => Math.round(n * 100) / 100
|
||||
|
||||
// Make it easier to click around from center ("click [from] zero zero")
|
||||
const click00 = (x: number, y: number) =>
|
||||
opts.page.mouse.click(opts.center.x + x, opts.center.y + y, { delay: 100 })
|
||||
opts.page.mouse.click(x, y, { delay: 100 })
|
||||
|
||||
// Relative clicker, must keep state
|
||||
let last = { x: 0, y: 0 }
|
||||
let lastScreenSpace = { x: 0, y: 0 }
|
||||
|
||||
const click00r = async (x?: number, y?: number) => {
|
||||
// reset relative coordinates when anything is undefined
|
||||
if (x === undefined || y === undefined) {
|
||||
last.x = 0
|
||||
last.y = 0
|
||||
return
|
||||
last = { x: 0, y: 0 }
|
||||
lastScreenSpace = { x: 0, y: 0 }
|
||||
return {
|
||||
nextXY: [0, 0],
|
||||
kcl: `[0, 0]`,
|
||||
}
|
||||
}
|
||||
|
||||
await circleMove(
|
||||
opts.page,
|
||||
opts.center.x + last.x + x,
|
||||
opts.center.y + last.y + y,
|
||||
10,
|
||||
10
|
||||
const absX = opts.center.x + x
|
||||
const absY = opts.center.y + y
|
||||
|
||||
const nextX = last.x + x
|
||||
const nextY = last.y + y
|
||||
|
||||
const targetX = opts.center.x + nextX
|
||||
const targetY = opts.center.y + -nextY
|
||||
|
||||
// Use the current camera specification
|
||||
const camera = await opts.page.evaluate(() => {
|
||||
window.sceneInfra.camControls.onCameraChange(true)
|
||||
return window.sceneInfra.camControls.camera
|
||||
})
|
||||
|
||||
const windowWH = await opts.page.evaluate(() => ({
|
||||
w: window.innerWidth,
|
||||
h: window.innerHeight,
|
||||
}))
|
||||
|
||||
// I didn't write this math, it's copied from sceneInfra.ts, and I understand
|
||||
// it's just normalizing the point, but why *-2 ± 1 I have no idea.
|
||||
const mouseVector = new Vector2(
|
||||
(targetX / windowWH.w) * 2 - 1,
|
||||
-(targetY / windowWH.h) * 2 + 1
|
||||
)
|
||||
await click00(last.x + x, last.y + y)
|
||||
planeRaycaster.setFromCamera(mouseVector, camera)
|
||||
const intersections = planeRaycaster.intersectObjects(scene.children, true)
|
||||
|
||||
const planePosition = intersections[0].object.position
|
||||
const inversePlaneQuaternion = intersections[0].object.quaternion
|
||||
.clone()
|
||||
.invert()
|
||||
let transformedPoint = intersections[0].point.clone()
|
||||
if (transformedPoint) {
|
||||
transformedPoint.applyQuaternion(inversePlaneQuaternion)
|
||||
}
|
||||
const twoD = new Vector2(
|
||||
// I think the intersection plane doesn't get scale when nearly everything else does, maybe that should change
|
||||
transformedPoint.x / sceneInfra._baseUnitMultiplier,
|
||||
transformedPoint.y / sceneInfra._baseUnitMultiplier
|
||||
) // z should be 0
|
||||
const planePositionCorrected = new Vector3(
|
||||
...planePosition
|
||||
).applyQuaternion(inversePlaneQuaternion)
|
||||
twoD.sub(new Vector2(...planePositionCorrected))
|
||||
|
||||
await circleMove(opts.page, targetX, targetY, 10, 10)
|
||||
await click00(targetX, targetY)
|
||||
|
||||
last.x += x
|
||||
last.y += y
|
||||
|
||||
// Returns the new absolute coordinate if you need it.
|
||||
return [last.x, last.y]
|
||||
const relativeScreenSpace = {
|
||||
x: twoD.x - lastScreenSpace.x,
|
||||
y: -(twoD.y - lastScreenSpace.y),
|
||||
}
|
||||
|
||||
lastScreenSpace.x = kcRound(twoD.x)
|
||||
lastScreenSpace.y = kcRound(twoD.y)
|
||||
|
||||
// Returns the new absolute coordinate and the screen space coordinate if you need it.
|
||||
return {
|
||||
nextXY: [last.x, last.y],
|
||||
kcl: `[${kcRound(relativeScreenSpace.x)}, ${-kcRound(
|
||||
relativeScreenSpace.y
|
||||
)}]`,
|
||||
}
|
||||
}
|
||||
|
||||
return { toSU, click00r }
|
||||
return { toSU, toU, click00r }
|
||||
}
|
||||
|
||||
async function waitForAuthAndLsp(page: Page) {
|
||||
@ -337,6 +457,30 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
||||
|
||||
const util = {
|
||||
async getModelViewAreaSize() {
|
||||
const windowInnerWidth = await page.evaluate(() => window.innerWidth)
|
||||
const windowInnerHeight = await page.evaluate(() => window.innerHeight)
|
||||
|
||||
const sidebar = page.getByTestId('modeling-sidebar')
|
||||
const bb = await sidebar.boundingBox()
|
||||
return {
|
||||
w: windowInnerWidth - (bb?.width ?? 0),
|
||||
h: windowInnerHeight - (bb?.height ?? 0),
|
||||
}
|
||||
},
|
||||
async getCenterOfModelViewArea() {
|
||||
const windowInnerWidth = await page.evaluate(() => window.innerWidth)
|
||||
const windowInnerHeight = await page.evaluate(() => window.innerHeight)
|
||||
|
||||
const sidebar = page.getByTestId('modeling-sidebar')
|
||||
const bb = await sidebar.boundingBox()
|
||||
const goRightPx = (bb?.width ?? 0) / 2
|
||||
const borderWidthsCombined = 2
|
||||
return {
|
||||
x: Math.round(windowInnerWidth / 2 + goRightPx) - borderWidthsCombined,
|
||||
y: Math.round(windowInnerHeight / 2),
|
||||
}
|
||||
},
|
||||
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||
waitForPageLoad: () => waitForPageLoad(page),
|
||||
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
|
||||
@ -506,6 +650,9 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: name })
|
||||
.click()
|
||||
await expect(page.getByTestId('project-sidebar-toggle')).toContainText(
|
||||
name
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -43,10 +43,12 @@ test.describe('Testing constraints', () => {
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(500) // wait for animation
|
||||
|
||||
const startXPx = 500
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
|
||||
const startXPx = center.x - 100
|
||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.click(834, 244)
|
||||
await page.mouse.click(center.x + 234, 244)
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
await page
|
||||
|
@ -32,10 +32,17 @@ test.describe('Testing selections', () => {
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const xAxisClick = () =>
|
||||
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
|
||||
const yAxisClick = () =>
|
||||
test.step('Click on Y axis', async () => {
|
||||
await page.mouse.move(600, 200, { steps: 5 })
|
||||
await page.mouse.click(600, 200)
|
||||
await page.waitForTimeout(100)
|
||||
})
|
||||
const xAxisClickAfterExitingSketch = () =>
|
||||
page.mouse.click(639, 278).then(() => page.waitForTimeout(100))
|
||||
test.step(`Click on X axis after exiting sketch, which shifts it at the moment`, async () => {
|
||||
await page.mouse.click(639, 278)
|
||||
await page.waitForTimeout(100)
|
||||
})
|
||||
const emptySpaceHover = () =>
|
||||
test.step('Hover over empty space', async () => {
|
||||
await page.mouse.move(700, 143, { steps: 5 })
|
||||
@ -80,23 +87,23 @@ test.describe('Testing selections', () => {
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
||||
|> line([-${commonPoints.num2}, 0], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
|
||||
// deselect line tool
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
@ -121,51 +128,58 @@ test.describe('Testing selections', () => {
|
||||
// now check clicking works including axis
|
||||
|
||||
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
||||
await topHorzSegmentClick()
|
||||
await page.keyboard.down('Shift')
|
||||
const constrainButton = page.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
const absYButton = page.getByRole('button', { name: 'Absolute Y' })
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).toBeDisabled()
|
||||
await page.waitForTimeout(100)
|
||||
await xAxisClick()
|
||||
await page.keyboard.up('Shift')
|
||||
await constrainButton.click()
|
||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
||||
await expect(absYButton).not.toBeDisabled()
|
||||
const absXButton = page.getByRole('button', { name: 'Absolute X' })
|
||||
|
||||
await test.step(`Select a segment and an axis, see that a relevant constraint is enabled`, async () => {
|
||||
await topHorzSegmentClick()
|
||||
await page.keyboard.down('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absXButton).toBeDisabled()
|
||||
await page.waitForTimeout(100)
|
||||
await yAxisClick()
|
||||
await page.keyboard.up('Shift')
|
||||
await constrainButton.click()
|
||||
await absXButton.and(page.locator(':not([disabled])')).waitFor()
|
||||
await expect(absXButton).not.toBeDisabled()
|
||||
})
|
||||
|
||||
// clear selection by clicking on nothing
|
||||
await emptySpaceClick()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
// same selection but click the axis first
|
||||
await xAxisClick()
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).toBeDisabled()
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
await topHorzSegmentClick()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.keyboard.up('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).not.toBeDisabled()
|
||||
await test.step(`Same selection but click the axis first`, async () => {
|
||||
await yAxisClick()
|
||||
await constrainButton.click()
|
||||
await expect(absXButton).toBeDisabled()
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
await topHorzSegmentClick()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.keyboard.up('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absXButton).not.toBeDisabled()
|
||||
})
|
||||
|
||||
// clear selection by clicking on nothing
|
||||
await emptySpaceClick()
|
||||
|
||||
// check the same selection again by putting cursor in code first then selecting axis
|
||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||
await page.keyboard.down('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).toBeDisabled()
|
||||
await page.waitForTimeout(100)
|
||||
await xAxisClick()
|
||||
await page.keyboard.up('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).not.toBeDisabled()
|
||||
await test.step(`Same selection but code selection then axis`, async () => {
|
||||
await page
|
||||
.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
.click()
|
||||
await page.keyboard.down('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absXButton).toBeDisabled()
|
||||
await page.waitForTimeout(100)
|
||||
await yAxisClick()
|
||||
await page.keyboard.up('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absXButton).not.toBeDisabled()
|
||||
})
|
||||
|
||||
// clear selection by clicking on nothing
|
||||
await emptySpaceClick()
|
||||
@ -180,7 +194,7 @@ test.describe('Testing selections', () => {
|
||||
process.platform === 'linux' ? 'Control' : 'Meta'
|
||||
)
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||
await page.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`).click()
|
||||
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||
await page.waitForTimeout(500)
|
||||
@ -924,6 +938,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
||||
// test fillet button with the body in the scene
|
||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||
extrude001 = extrude(10, sketch001)`
|
||||
await u.codeLocator.clear()
|
||||
await u.codeLocator.fill(codeToAdd)
|
||||
await selectSegment()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
|
@ -258,7 +258,7 @@ test.describe('Testing settings', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
`Project settings override user settings on desktop`,
|
||||
{ tag: ['@electron', '@skipWin'] },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
@ -266,10 +266,15 @@ test.describe('Testing settings', () => {
|
||||
process.platform === 'win32',
|
||||
'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,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
const bracketDir = join(dir, projectName)
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
@ -281,6 +286,12 @@ test.describe('Testing settings', () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Selectors and constants
|
||||
const tempProjectSettingsFilePath = join(
|
||||
projectDirName,
|
||||
projectName,
|
||||
PROJECT_SETTINGS_FILE_NAME
|
||||
)
|
||||
const tempUserSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
|
||||
const userThemeColor = '120'
|
||||
const projectThemeColor = '50'
|
||||
const settingsOpenButton = page.getByRole('link', {
|
||||
@ -301,6 +312,12 @@ test.describe('Testing settings', () => {
|
||||
await themeColorSetting.fill(userThemeColor)
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
|
||||
await settingsCloseButton.click()
|
||||
await expect
|
||||
.poll(async () => fsp.readFile(tempUserSettingsFilePath, 'utf-8'), {
|
||||
message: 'Setting should now be written to the file',
|
||||
timeout: 5_000,
|
||||
})
|
||||
.toContain(`themeColor = "${userThemeColor}"`)
|
||||
})
|
||||
|
||||
await test.step('Set project theme color', async () => {
|
||||
@ -312,13 +329,23 @@ test.describe('Testing settings', () => {
|
||||
await themeColorSetting.fill(projectThemeColor)
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
||||
await settingsCloseButton.click()
|
||||
// Make sure that the project settings file has been written to before continuing
|
||||
await expect
|
||||
.poll(
|
||||
async () => fsp.readFile(tempProjectSettingsFilePath, 'utf-8'),
|
||||
{
|
||||
message: 'Setting should now be written to the file',
|
||||
timeout: 5_000,
|
||||
}
|
||||
)
|
||||
.toContain(`themeColor = "${projectThemeColor}"`)
|
||||
})
|
||||
|
||||
await test.step('Refresh the application and see project setting applied', async () => {
|
||||
// Make sure we're done navigating before we reload
|
||||
await expect(settingsCloseButton).not.toBeVisible()
|
||||
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||
|
||||
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
||||
})
|
||||
|
||||
@ -387,7 +414,7 @@ test.describe('Testing settings', () => {
|
||||
)
|
||||
|
||||
// It was much easier to test the logo color than the background stream color.
|
||||
test(
|
||||
test.fixme(
|
||||
'user settings reload on external change, on project and modeling view',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -716,18 +743,19 @@ extrude001 = extrude(5, sketch001)
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Selectors and constants
|
||||
const editSketchButton = page.getByRole('button', { name: 'Edit Sketch' })
|
||||
const lineToolButton = page.getByTestId('line')
|
||||
const segmentOverlays = page.getByTestId('segment-overlay')
|
||||
const sketchOriginLocation = { x: 600, y: 250 }
|
||||
const sketchOriginLocation = await u.getCenterOfModelViewArea()
|
||||
const darkThemeSegmentColor: [number, number, number] = [215, 215, 215]
|
||||
const lightThemeSegmentColor: [number, number, number] = [90, 90, 90]
|
||||
|
||||
await test.step(`Get into sketch mode`, async () => {
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.click(sketchOriginLocation.x, sketchOriginLocation.y)
|
||||
|
||||
await expect(editSketchButton).toBeVisible()
|
||||
await editSketchButton.click()
|
||||
|
||||
@ -738,12 +766,18 @@ extrude001 = extrude(5, sketch001)
|
||||
await page.waitForTimeout(1000)
|
||||
})
|
||||
|
||||
const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
|
||||
|
||||
// Our lines are translucent (surprise!), so we need to get on portion
|
||||
// of the line that is only on the background, and not on top of something
|
||||
// like the axis lines.
|
||||
line1.x -= 1
|
||||
line1.y -= 1
|
||||
|
||||
await test.step(`Check the sketch line color before`, async () => {
|
||||
await expect
|
||||
.poll(() =>
|
||||
u.getGreatestPixDiff(sketchOriginLocation, darkThemeSegmentColor)
|
||||
)
|
||||
.toBeLessThan(15)
|
||||
.poll(() => u.getGreatestPixDiff(line1, darkThemeSegmentColor))
|
||||
.toBeLessThanOrEqual(34)
|
||||
})
|
||||
|
||||
await test.step(`Change theme to light using command palette`, async () => {
|
||||
@ -758,10 +792,8 @@ extrude001 = extrude(5, sketch001)
|
||||
|
||||
await test.step(`Check the sketch line color after`, async () => {
|
||||
await expect
|
||||
.poll(() =>
|
||||
u.getGreatestPixDiff(sketchOriginLocation, lightThemeSegmentColor)
|
||||
)
|
||||
.toBeLessThan(15)
|
||||
.poll(() => u.getGreatestPixDiff(line1, lightThemeSegmentColor))
|
||||
.toBeLessThanOrEqual(34)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -256,181 +256,186 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
test.fixme(
|
||||
'Basic default modeling and sketch hotkeys work',
|
||||
async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// This test can run long if it takes a little too long to load
|
||||
// the engine.
|
||||
test.setTimeout(90000)
|
||||
// This test has a weird bug on ubuntu
|
||||
test.skip(
|
||||
process.platform === 'linux',
|
||||
'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444'
|
||||
)
|
||||
// Load the app with the code pane open
|
||||
// This test can run long if it takes a little too long to load
|
||||
// the engine.
|
||||
test.setTimeout(90000)
|
||||
// This test has a weird bug on ubuntu
|
||||
// Funny, it's flaking on Windows too :). I think there is just something
|
||||
// actually wrong.
|
||||
test.skip(
|
||||
process.platform === 'linux',
|
||||
'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444'
|
||||
)
|
||||
// Load the app with the code pane open
|
||||
|
||||
await test.step(`Set up test`, async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'store',
|
||||
JSON.stringify({
|
||||
state: {
|
||||
openPanes: ['code'],
|
||||
},
|
||||
version: 0,
|
||||
await test.step(`Set up test`, async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'store',
|
||||
JSON.stringify({
|
||||
state: {
|
||||
openPanes: ['code'],
|
||||
},
|
||||
version: 0,
|
||||
})
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
})
|
||||
|
||||
const codePane = page.locator('.cm-content')
|
||||
const lineButton = page.getByRole('button', {
|
||||
name: 'line Line',
|
||||
exact: true,
|
||||
})
|
||||
const arcButton = page.getByRole('button', {
|
||||
name: 'arc Tangential Arc',
|
||||
exact: true,
|
||||
})
|
||||
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
|
||||
const commandBarComboBox = page.getByPlaceholder('Search commands')
|
||||
const exitSketchButton = page.getByRole('button', { name: 'Exit Sketch' })
|
||||
|
||||
await test.step(`Type code with modeling hotkeys, shouldn't fire`, async () => {
|
||||
await codePane.click()
|
||||
await page.keyboard.type('//')
|
||||
await page.keyboard.press('s')
|
||||
await expect(commandBarComboBox).not.toBeVisible()
|
||||
await page.keyboard.press('e')
|
||||
await expect(commandBarComboBox).not.toBeVisible()
|
||||
await expect(codePane).toHaveText('//se')
|
||||
})
|
||||
|
||||
// Blur focus from the code editor, use the s command to sketch
|
||||
await test.step(`Blur editor focus, enter sketch`, async () => {
|
||||
/**
|
||||
* TODO: There is a bug somewhere that causes this test to fail
|
||||
* if you toggle the codePane closed before your trigger the
|
||||
* start of the sketch.
|
||||
* and a separate Safari-only bug that causes the test to fail
|
||||
* if the pane is open the entire test. The maintainer of CodeMirror
|
||||
* has pinpointed this to the unusual browser behavior:
|
||||
* https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3
|
||||
*/
|
||||
await blurCodeEditor()
|
||||
await page.waitForTimeout(1000)
|
||||
await page.keyboard.press('s')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.mouse.move(800, 300, { steps: 5 })
|
||||
await page.mouse.click(800, 300)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
||||
timeout: 15_000,
|
||||
})
|
||||
})
|
||||
|
||||
// Use some sketch hotkeys to create a sketch (l and a for now)
|
||||
await test.step(`Incomplete sketch with hotkeys`, async () => {
|
||||
await test.step(`Draw a line`, async () => {
|
||||
await page.mouse.move(700, 200, { steps: 5 })
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.move(800, 250, { steps: 5 })
|
||||
await page.mouse.click(800, 250)
|
||||
})
|
||||
|
||||
await test.step(`Unequip line tool`, async () => {
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
|
||||
await test.step(`Draw a tangential arc`, async () => {
|
||||
await page.keyboard.press('a')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true', {
|
||||
timeout: 10_000,
|
||||
})
|
||||
)
|
||||
await page.mouse.move(1000, 100, { steps: 5 })
|
||||
await page.mouse.click(1000, 100)
|
||||
})
|
||||
|
||||
await test.step(`Unequip with escape, equip line tool`, async () => {
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('l')
|
||||
await page.waitForTimeout(50)
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
})
|
||||
|
||||
const codePane = page.locator('.cm-content')
|
||||
const lineButton = page.getByRole('button', {
|
||||
name: 'line Line',
|
||||
exact: true,
|
||||
})
|
||||
const arcButton = page.getByRole('button', {
|
||||
name: 'arc Tangential Arc',
|
||||
exact: true,
|
||||
})
|
||||
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
|
||||
const commandBarComboBox = page.getByPlaceholder('Search commands')
|
||||
const exitSketchButton = page.getByRole('button', { name: 'Exit Sketch' })
|
||||
await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => {
|
||||
// Since there's code now, we have to get to the end of the line
|
||||
await page.locator('.cm-line').last().click()
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('ArrowRight')
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await test.step(`Type code with modeling hotkeys, shouldn't fire`, async () => {
|
||||
await codePane.click()
|
||||
await page.keyboard.type('//')
|
||||
await page.keyboard.press('s')
|
||||
await expect(commandBarComboBox).not.toBeVisible()
|
||||
await page.keyboard.press('e')
|
||||
await expect(commandBarComboBox).not.toBeVisible()
|
||||
await expect(codePane).toHaveText('//se')
|
||||
})
|
||||
|
||||
// Blur focus from the code editor, use the s command to sketch
|
||||
await test.step(`Blur editor focus, enter sketch`, async () => {
|
||||
/**
|
||||
* TODO: There is a bug somewhere that causes this test to fail
|
||||
* if you toggle the codePane closed before your trigger the
|
||||
* start of the sketch.
|
||||
* and a separate Safari-only bug that causes the test to fail
|
||||
* if the pane is open the entire test. The maintainer of CodeMirror
|
||||
* has pinpointed this to the unusual browser behavior:
|
||||
* https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3
|
||||
*/
|
||||
await blurCodeEditor()
|
||||
await page.waitForTimeout(1000)
|
||||
await page.keyboard.press('s')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.mouse.move(800, 300, { steps: 5 })
|
||||
await page.mouse.click(800, 300)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
||||
timeout: 15_000,
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('//')
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await page.keyboard.press('a')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(codePane).toContainText('//la')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Backspace')
|
||||
})
|
||||
})
|
||||
|
||||
// Use some sketch hotkeys to create a sketch (l and a for now)
|
||||
await test.step(`Incomplete sketch with hotkeys`, async () => {
|
||||
await test.step(`Draw a line`, async () => {
|
||||
await test.step(`Close profile and exit sketch`, async () => {
|
||||
await blurCodeEditor()
|
||||
await page.mouse.move(700, 200, { steps: 5 })
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.move(800, 250, { steps: 5 })
|
||||
await page.mouse.click(800, 250)
|
||||
})
|
||||
|
||||
await test.step(`Unequip line tool`, async () => {
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
|
||||
await test.step(`Draw a tangential arc`, async () => {
|
||||
await page.keyboard.press('a')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true', {
|
||||
timeout: 10_000,
|
||||
})
|
||||
await page.mouse.move(1000, 100, { steps: 5 })
|
||||
await page.mouse.click(1000, 100)
|
||||
})
|
||||
|
||||
await test.step(`Unequip with escape, equip line tool`, async () => {
|
||||
// On close it will unequip the line tool.
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
|
||||
await expect(exitSketchButton).toBeEnabled()
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('l')
|
||||
await page.waitForTimeout(50)
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => {
|
||||
// Since there's code now, we have to get to the end of the line
|
||||
await page.locator('.cm-line').last().click()
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('ArrowRight')
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('//')
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await page.keyboard.press('a')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(codePane).toContainText('//la')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Backspace')
|
||||
})
|
||||
|
||||
await test.step(`Close profile and exit sketch`, async () => {
|
||||
await blurCodeEditor()
|
||||
await page.mouse.move(700, 200, { steps: 5 })
|
||||
await page.mouse.click(700, 200)
|
||||
// On close it will unequip the line tool.
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
|
||||
await expect(exitSketchButton).toBeEnabled()
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
// Extrude with e
|
||||
await test.step(`Extrude the sketch`, async () => {
|
||||
await page.mouse.click(750, 150)
|
||||
await blurCodeEditor()
|
||||
await expect(extrudeButton).toBeEnabled()
|
||||
await page.keyboard.press('e')
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.move(800, 200, { steps: 5 })
|
||||
await page.mouse.click(800, 200)
|
||||
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({
|
||||
timeout: 20_000,
|
||||
// Extrude with e
|
||||
await test.step(`Extrude the sketch`, async () => {
|
||||
await page.mouse.click(750, 150)
|
||||
await blurCodeEditor()
|
||||
await expect(extrudeButton).toBeEnabled()
|
||||
await page.keyboard.press('e')
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.move(800, 200, { steps: 5 })
|
||||
await page.mouse.click(800, 200)
|
||||
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({
|
||||
timeout: 20_000,
|
||||
})
|
||||
await page.getByRole('button', { name: 'Continue' }).click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Submit command' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
||||
})
|
||||
await page.getByRole('button', { name: 'Continue' }).click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Submit command' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
||||
})
|
||||
|
||||
// await codePaneButton.click()
|
||||
// await expect(u.codeLocator).not.toBeVisible()
|
||||
// await codePaneButton.click()
|
||||
// await expect(u.codeLocator).not.toBeVisible()
|
||||
|
||||
/**
|
||||
* work-around: to stop `keyboard.press()` from typing in the editor even when it should be blurred
|
||||
*/
|
||||
async function blurCodeEditor() {
|
||||
await page.getByRole('button', { name: 'Commands' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Escape')
|
||||
await page.waitForTimeout(100)
|
||||
/**
|
||||
* work-around: to stop `keyboard.press()` from typing in the editor even when it should be blurred
|
||||
*/
|
||||
async function blurCodeEditor() {
|
||||
await page.getByRole('button', { name: 'Commands' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Escape')
|
||||
await page.waitForTimeout(100)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
test('Delete key does not navigate back', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -498,14 +503,16 @@ test('Sketch on face', async ({ page }) => {
|
||||
|
||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
|
||||
// This basically waits for sketch mode to be ready.
|
||||
await u.doAndWaitForCmd(
|
||||
() => page.mouse.click(625, 165),
|
||||
async () => page.mouse.click(center.x, 180),
|
||||
'default_camera_get_settings',
|
||||
true
|
||||
)
|
||||
await page.waitForTimeout(150)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
const firstClickPosition = [612, 238]
|
||||
const secondClickPosition = [661, 242]
|
||||
|
1
interface.d.ts
vendored
@ -78,6 +78,7 @@ export interface IElectronAPI {
|
||||
) => Electron.IpcRenderer
|
||||
onUpdateError: (callback: (value: { error: Error }) => void) => Electron
|
||||
appRestart: () => void
|
||||
getArgvParsed: () => any
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
29
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zoo-modeling-app",
|
||||
"version": "0.26.2",
|
||||
"version": "0.26.5",
|
||||
"private": true,
|
||||
"productName": "Zoo Modeling App",
|
||||
"author": {
|
||||
@ -14,7 +14,7 @@
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
"@codemirror/commands": "^6.6.0",
|
||||
"@codemirror/language": "^6.10.2",
|
||||
"@codemirror/language": "^6.10.3",
|
||||
"@codemirror/lint": "^6.8.1",
|
||||
"@codemirror/search": "^6.5.6",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
@ -40,7 +40,7 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"electron-updater": "^6.3.0",
|
||||
"electron-updater": "^6.3.9",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
@ -56,16 +56,17 @@
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-modal-promise": "^1.0.2",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"react-router-dom": "^6.27.0",
|
||||
"sketch-helpers": "^0.0.4",
|
||||
"three": "^0.166.1",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"uuid": "^9.0.1",
|
||||
"uuid": "^11.0.2",
|
||||
"vscode-jsonrpc": "^8.2.1",
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"vscode-uri": "^3.0.8",
|
||||
"web-vitals": "^3.5.2",
|
||||
"xstate": "^5.17.4"
|
||||
"xstate": "^5.17.4",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
@ -76,7 +77,7 @@
|
||||
"build:both": "vite build",
|
||||
"build:both:local": "yarn build:wasm && vite build",
|
||||
"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:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
@ -105,7 +106,8 @@
|
||||
"tronb:package": "electron-builder --config electron-builder.yml",
|
||||
"test-setup": "yarn install && yarn build:wasm",
|
||||
"test": "vitest --mode development",
|
||||
"test:unit": "vitest run --mode development",
|
||||
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
||||
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
||||
"test:playwright:browser:chrome": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron'",
|
||||
"test:playwright:browser:chrome:windows": "playwright test --project=\"Google Chrome\" --config=playwright.ci.config.ts --grep-invert=\"@snapshot|@electron|@skipWin\"",
|
||||
"test:playwright:browser:chrome:ubuntu": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron|@skipLinux'",
|
||||
@ -117,7 +119,8 @@
|
||||
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin",
|
||||
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos",
|
||||
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux",
|
||||
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000"
|
||||
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
||||
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
@ -144,8 +147,8 @@
|
||||
"@electron-forge/maker-deb": "^7.4.0",
|
||||
"@electron-forge/maker-rpm": "^7.4.0",
|
||||
"@electron-forge/maker-squirrel": "^7.4.0",
|
||||
"@electron-forge/maker-wix": "^7.4.0",
|
||||
"@electron-forge/maker-zip": "^7.4.0",
|
||||
"@electron-forge/maker-wix": "^7.5.0",
|
||||
"@electron-forge/maker-zip": "^7.5.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
||||
"@electron-forge/plugin-fuses": "^7.4.0",
|
||||
"@electron-forge/plugin-vite": "^7.4.0",
|
||||
@ -171,7 +174,7 @@
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@types/ws": "^8.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
@ -187,7 +190,7 @@
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||
"happy-dom": "^14.3.10",
|
||||
"happy-dom": "^15.10.2",
|
||||
"http-server": "^14.1.1",
|
||||
"husky": "^9.1.5",
|
||||
"kill-port": "^2.0.1",
|
||||
|
@ -34,6 +34,11 @@
|
||||
"title": "Car Wheel Assembly",
|
||||
"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",
|
||||
"title": "Enclosure",
|
||||
@ -54,6 +59,11 @@
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"file": "food-service-spatula.kcl",
|
||||
"title": "Food Service Spatula",
|
||||
"description": "Use these spatulas for mixing, flipping, and scraping."
|
||||
},
|
||||
{
|
||||
"file": "french-press.kcl",
|
||||
"title": "French Press",
|
||||
@ -61,7 +71,7 @@
|
||||
},
|
||||
{
|
||||
"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."
|
||||
},
|
||||
{
|
||||
|
50
src/App.tsx
@ -1,15 +1,14 @@
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||
import { Stream } from './components/Stream'
|
||||
import { AppHeader } from './components/AppHeader'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||
import { codeManager, engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { useLspContext } from 'components/LspProvider'
|
||||
@ -22,6 +21,12 @@ import Gizmo from 'components/Gizmo'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { UnitsMenu } from 'components/UnitsMenu'
|
||||
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
|
||||
import EngineStreamContext from 'hooks/useEngineStreamContext'
|
||||
import { EngineStream } from 'components/EngineStream'
|
||||
import { maybeWriteToDisk } from 'lib/telemetry'
|
||||
maybeWriteToDisk()
|
||||
.then(() => {})
|
||||
.catch(() => {})
|
||||
|
||||
export function App() {
|
||||
const { project, file } = useLoaderData() as IndexLoaderData
|
||||
@ -33,6 +38,13 @@ export function App() {
|
||||
// the coredump.
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Stream related refs and data
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const modelingSidebarRef = useRef<HTMLUListElement>(null)
|
||||
let [searchParams] = useSearchParams()
|
||||
const pool = searchParams.get('pool')
|
||||
|
||||
const projectName = project?.name || null
|
||||
const projectPath = project?.path || null
|
||||
useEffect(() => {
|
||||
@ -53,6 +65,10 @@ export function App() {
|
||||
app: { onboardingStatus },
|
||||
} = settings.context
|
||||
|
||||
useEffect(() => {
|
||||
sceneInfra.camControls.modelingSidebarRef = modelingSidebarRef
|
||||
}, [modelingSidebarRef.current])
|
||||
|
||||
useHotkeys('backspace', (e) => {
|
||||
e.preventDefault()
|
||||
})
|
||||
@ -80,14 +96,26 @@ export function App() {
|
||||
enableMenu={true}
|
||||
/>
|
||||
<ModalContainer />
|
||||
<ModelingSidebar paneOpacity={paneOpacity} />
|
||||
<Stream />
|
||||
{/* <CamToggle /> */}
|
||||
<LowerRightControls coreDumpManager={coreDumpManager}>
|
||||
<UnitsMenu />
|
||||
<Gizmo />
|
||||
<CameraProjectionToggle />
|
||||
</LowerRightControls>
|
||||
<ModelingSidebar paneOpacity={paneOpacity} ref={modelingSidebarRef} />
|
||||
<EngineStreamContext.Provider
|
||||
options={{
|
||||
input: {
|
||||
videoRef,
|
||||
canvasRef,
|
||||
mediaStream: null,
|
||||
authToken: auth?.context?.token ?? null,
|
||||
pool,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<EngineStream />
|
||||
{/* <CamToggle /> */}
|
||||
<LowerRightControls coreDumpManager={coreDumpManager}>
|
||||
<UnitsMenu />
|
||||
<Gizmo />
|
||||
<CameraProjectionToggle />
|
||||
</LowerRightControls>
|
||||
</EngineStreamContext.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
} from 'react-router-dom'
|
||||
import { ErrorPage } from './components/ErrorPage'
|
||||
import { Settings } from './routes/Settings'
|
||||
import { Telemetry } from './routes/Telemetry'
|
||||
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
|
||||
import SignIn from './routes/SignIn'
|
||||
import { Auth } from './Auth'
|
||||
@ -28,6 +29,7 @@ import {
|
||||
homeLoader,
|
||||
onboardingRedirectLoader,
|
||||
settingsLoader,
|
||||
telemetryLoader,
|
||||
} from 'lib/routeLoaders'
|
||||
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
|
||||
import SettingsAuthProvider from 'components/SettingsAuthProvider'
|
||||
@ -43,6 +45,7 @@ import { coreDump } from 'lang/wasm'
|
||||
import { useMemo } from 'react'
|
||||
import { AppStateProvider } from 'AppState'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { RouteProvider } from 'components/RouteProvider'
|
||||
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
|
||||
|
||||
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
||||
@ -56,19 +59,21 @@ const router = createRouter([
|
||||
* inefficient re-renders, use the react profiler to see. */
|
||||
element: (
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthProvider>
|
||||
<LspProvider>
|
||||
<ProjectsContextProvider>
|
||||
<KclContextProvider>
|
||||
<AppStateProvider>
|
||||
<MachineManagerProvider>
|
||||
<Outlet />
|
||||
</MachineManagerProvider>
|
||||
</AppStateProvider>
|
||||
</KclContextProvider>
|
||||
</ProjectsContextProvider>
|
||||
</LspProvider>
|
||||
</SettingsAuthProvider>
|
||||
<RouteProvider>
|
||||
<SettingsAuthProvider>
|
||||
<LspProvider>
|
||||
<ProjectsContextProvider>
|
||||
<KclContextProvider>
|
||||
<AppStateProvider>
|
||||
<MachineManagerProvider>
|
||||
<Outlet />
|
||||
</MachineManagerProvider>
|
||||
</AppStateProvider>
|
||||
</KclContextProvider>
|
||||
</ProjectsContextProvider>
|
||||
</LspProvider>
|
||||
</SettingsAuthProvider>
|
||||
</RouteProvider>
|
||||
</CommandBarProvider>
|
||||
),
|
||||
errorElement: <ErrorPage />,
|
||||
@ -124,6 +129,16 @@ const router = createRouter([
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: PATHS.FILE + 'TELEMETRY',
|
||||
loader: telemetryLoader,
|
||||
children: [
|
||||
{
|
||||
path: makeUrlPathRelative(PATHS.TELEMETRY),
|
||||
element: <Telemetry />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -149,6 +164,11 @@ const router = createRouter([
|
||||
loader: settingsLoader,
|
||||
element: <Settings />,
|
||||
},
|
||||
{
|
||||
path: makeUrlPathRelative(PATHS.TELEMETRY),
|
||||
loader: telemetryLoader,
|
||||
element: <Telemetry />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
} from 'lib/toolbar'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { EngineConnectionStateType } from 'lang/std/engineConnection'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
@ -48,7 +49,7 @@ export function Toolbar({
|
||||
}, [engineCommandManager.artifactGraph, context.selectionRanges])
|
||||
|
||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||
const { overallState } = useNetworkContext()
|
||||
const { overallState, immediateState } = useNetworkContext()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { isStreamReady } = useAppState()
|
||||
|
||||
@ -56,6 +57,7 @@ export function Toolbar({
|
||||
(overallState !== NetworkHealthState.Ok &&
|
||||
overallState !== NetworkHealthState.Weak) ||
|
||||
isExecuting ||
|
||||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished ||
|
||||
!isStreamReady
|
||||
|
||||
const currentMode =
|
||||
|