Compare commits

..

6 Commits

763 changed files with 27558 additions and 486154 deletions

View File

@ -1,3 +1,3 @@
[codespell]
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock

View File

@ -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."

View File

@ -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."

View File

@ -8,21 +8,21 @@ updates:
- package-ecosystem: 'npm' # See documentation for possible values
directory: '/' # Location of package manifests
schedule:
interval: 'weekly'
interval: 'daily'
reviewers:
- franknoirot
- irev-dev
- package-ecosystem: 'github-actions' # See documentation for possible values
directory: '/' # Location of package manifests
schedule:
interval: 'weekly'
interval: 'daily'
reviewers:
- adamchalmers
- jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src/wasm-lib/' # Location of package manifests
schedule:
interval: 'weekly'
interval: 'daily'
reviewers:
- adamchalmers
- jessfraz

View File

@ -15,7 +15,6 @@ on:
env:
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -52,20 +51,12 @@ jobs:
run: |
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
- name: Generate release notes
run: |
echo "$NOTES" > release-notes.md
cat release-notes.md
- uses: actions/upload-artifact@v3
with:
name: prepared-files
path: |
package.json
src/wasm-lib/pkg/wasm_lib*
release-notes.md
- id: export_version
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
@ -100,17 +91,20 @@ jobs:
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
platform: mac
- os: windows-2022
platform: win
- os: ubuntu-22.04
platform: linux
os: [macos-14, windows-2022, ubuntu-22.04]
runs-on: ${{ matrix.os }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D
steps:
- uses: actions/checkout@v4
@ -124,7 +118,6 @@ jobs:
cp prepared-files/src/wasm-lib/pkg/wasm_lib_bg.wasm public
mkdir src/wasm-lib/pkg
cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
cp prepared-files/release-notes.md release-notes.md
- uses: actions/download-artifact@v3
if: ${{ github.event_name == 'schedule' }}
@ -172,55 +165,25 @@ jobs:
smksp_cert_sync.exe
shell: cmd
- name: Build the app (debug)
if: ${{ env.BUILD_RELEASE == 'false' }}
# electron-builder doesn't have a concept of release vs debug,
# this is just not doing any codesign or release yml generation
run: yarn electron-builder --config
- name: Build the app (release)
if: ${{ env.BUILD_RELEASE == 'true' }}
env:
PUBLISH_FOR_PULL_REQUEST: true
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: yarn electron-builder --config --publish always
- name: Build the app
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
- name: List artifacts in out/
run: ls -R out
- uses: actions/upload-artifact@v3
with:
name: out-arm64-${{ matrix.platform }}
# first two will pick both Zoo Modeling App-$VERSION-arm64-win.exe and Zoo Modeling App-$VERSION-win.exe
path: |
out/*-${{ env.VERSION_NO_V }}-win.*
out/*-${{ env.VERSION_NO_V }}-arm64-win.*
out/*-arm64-mac.*
out/*-arm64-linux.*
- uses: actions/upload-artifact@v3
with:
name: out-x64-${{ matrix.platform }}
path: |
out/*-x64-win.*
out/*-x64-mac.*
out/*-x86_64-linux.*
- uses: actions/upload-artifact@v3
if: ${{ env.BUILD_RELEASE == 'true' }}
with:
name: out-yml
name: out-arm64-${{ matrix.os }}
path: |
out/Zoo*arm64*.*
out/latest*.yml
- uses: actions/upload-artifact@v3
with:
name: out-x64-${{ matrix.os }}
path: |
out/Zoo*x*64*.*
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
- uses: actions/download-artifact@v3
@ -235,35 +198,21 @@ jobs:
- name: Build the app (updater-test)
if: ${{ env.CUT_RELEASE_PR == 'true' }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: yarn electron-builder --config --publish always
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
- uses: actions/upload-artifact@v3
if: ${{ env.CUT_RELEASE_PR == 'true' }}
with:
name: updater-test-arm64-${{ matrix.platform }}
name: updater-test-arm64-${{ matrix.os }}
path: |
out/*-arm64-win.exe
out/*-arm64-mac.dmg
out/*-arm64-linux.AppImage
out/Zoo*arm64*.*
- uses: actions/upload-artifact@v3
if: ${{ env.CUT_RELEASE_PR == 'true' }}
with:
name: updater-test-x64-${{ matrix.platform }}
name: updater-test-x64-${{ matrix.os }}
path: |
out/*-x64-win.exe
out/*-x64-mac.dmg
out/*-x86_64-linux.AppImage
out/Zoo*x64*.*
publish-apps-release:
@ -276,6 +225,7 @@ jobs:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
@ -284,37 +234,32 @@ jobs:
- uses: actions/download-artifact@v3
with:
name: out-arm64-win
name: out-arm64-windows-2022
path: out
- uses: actions/download-artifact@v3
with:
name: out-x64-win
name: out-x64-windows-2022
path: out
- uses: actions/download-artifact@v3
with:
name: out-arm64-mac
name: out-arm64-macos-14
path: out
- uses: actions/download-artifact@v3
with:
name: out-x64-mac
name: out-x64-macos-14
path: out
- uses: actions/download-artifact@v3
with:
name: out-arm64-linux
name: out-arm64-ubuntu-22.04
path: out
- uses: actions/download-artifact@v3
with:
name: out-x64-linux
path: out
- uses: actions/download-artifact@v3
with:
name: out-yml
name: out-x64-ubuntu-22.04
path: out
- name: Generate the download static endpoint
@ -361,17 +306,17 @@ jobs:
run: "ls -R out"
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.7'
uses: 'google-github-actions/auth@v2.1.6'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
- name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2.1.2
uses: google-github-actions/setup-gcloud@v2.1.0
with:
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
- name: Upload release files to public bucket
uses: google-github-actions/upload-cloud-storage@v2.2.1
uses: google-github-actions/upload-cloud-storage@v2.2.0
with:
path: out
glob: 'Zoo*'
@ -379,7 +324,7 @@ jobs:
destination: ${{ env.BUCKET_DIR }}
- name: Upload update endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.2.1
uses: google-github-actions/upload-cloud-storage@v2.2.0
with:
path: out
glob: 'latest*'
@ -387,7 +332,7 @@ jobs:
destination: ${{ env.BUCKET_DIR }}
- name: Upload download endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.2.1
uses: google-github-actions/upload-cloud-storage@v2.2.0
with:
path: last_download.json
destination: ${{ env.BUCKET_DIR }}

View File

@ -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 --workspace --features disable-println --features pyo3 --features cli
cargo check --all --features disable-println --features pyo3 --features cli

View File

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

View File

@ -39,7 +39,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest-8-cores, windows-latest-8-cores]
os: [ubuntu-latest, windows-latest]
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
runs-on: ${{ matrix.os }}
@ -227,7 +227,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large]
os: [ubuntu-latest, windows-latest, macos-14]
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, 'ubuntu') }}
if: ${{ !startsWith(matrix.os, 'windows') }}
shell: bash
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh

View File

@ -37,6 +37,10 @@ jobs:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- run: yarn build:wasm
yarn-tsc:
@ -66,6 +70,10 @@ jobs:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- run: yarn lint
python-codespell:
@ -81,31 +89,6 @@ 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
@ -118,6 +101,11 @@ jobs:
cache: 'yarn'
- run: yarn install
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- run: yarn build:wasm
- run: yarn simpleserver:bg

2
.nvmrc
View File

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

View File

@ -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
yarn build:wasm-dev
node_modules: package.json yarn.lock
yarn install

View File

@ -2,7 +2,7 @@
## Zoo Modeling App
download at [zoo.dev/modeling-app/download](https://zoo.dev/modeling-app/download)
live at [app.zoo.dev](https://app.zoo.dev/)
A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
@ -57,7 +57,7 @@ yarn install
followed by:
```
yarn build:wasm
yarn build:wasm-dev
```
or if you have the gh cli installed
@ -66,15 +66,15 @@ or if you have the gh cli installed
./get-latest-wasm-bundle.sh # this will download the latest main wasm bundle
```
That will build the WASM binary and put in the `public` dir (though gitignored).
That will build the WASM binary and put in the `public` dir (though gitignored)
Finally, to run the web app only, run:
finally, to run the web app only, run:
```
yarn start
```
If you're not an KittyCAD employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens of course, then navigate to localhost:3000 again. Note that navigating to `localhost:3000/signin` removes your token so you will need to set the token again.
If you're not an KittyCAD employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens ofcourse, then navigate to localhost:3000 again. Note that navigating to localhost:3000/signin removes your token so you will need to set the token again.
### Development environment variables
@ -91,13 +91,13 @@ Third-Party Cookies".
## Desktop
To spin up the desktop app, `yarn install` and `yarn build:wasm` need to have been done before hand then
To spin up the desktop app, `yarn install` and `yarn build:wasm-dev` need to have been done before hand then
```
yarn tron:start
yarn electron:start
```
This will start the application and hot-reload on changes.
This will start the application and hot-reload on changed.
Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
@ -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
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
yarn start # or yarn build:local && yarn serve for slower but more production-like build
```
@ -128,18 +128,7 @@ Before you submit a contribution PR to this repo, please ensure that:
## Release a new version
#### 1. Bump the versions by running `./make-release.sh`
The `./make-release.sh` script has git commands to pull main but to be sure you can run the following git commands to have a fresh `main` locally.
```
git branch -D main
git checkout main
git pull origin
./make-release.sh
# Copy within the back ticks and paste the stdout of the change log
git push --set-upstream origin <branch name created from ./make-release.sh>
```
#### 1. Bump the versions by running `./make-release.sh` and create a Cut Release PR
That will create the branch with the updated json files for you:
- run `./make-release.sh` or `./make-release.sh patch` for a patch update;
@ -148,50 +137,28 @@ That will create the branch with the updated json files for you:
After it runs you should just need the push the branch and open a PR.
#### 2. Create a Cut Release PR
When you open the PR copy the change log from the output of the `./make-release.sh` script into the description of the PR.
**Important:** Pull request title needs to be prefixed with `Cut release v` to build in release mode and a few other things to test in the best context possible, the intent would be for instance to have `Cut release v1.2.3` for the `v1.2.3` release candidate.
**Important:** It needs to be prefixed with `Cut release v` to build in release mode and a few other things to test in the best context possible, the intent would be for instance to have `Cut release v1.2.3` for the `v1.2.3` release candidate.
The PR may then serve as a place to discuss the human-readable changelog and extra QA. The `make-release.sh` tool suggests a changelog for you too to be used as PR description, just make sure to delete lines that are not user facing.
#### 3. Manually test artifacts from the Cut Release PR
#### 2. Smoke test artifacts from the Cut Release PR
##### Release builds
The release builds can be find under the `artifact` zip, at the very bottom of the `ci` action page for each commit on this branch.
The release builds can be found under the `out-{platform}` zip, at the very bottom of the `build-publish-apps` summary page for each commit on this branch.
We don't have a strict process, but click around and check for anything obvious, posting results as comments in the Cut Release PR.
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the Cut Release PR.
The other `ci` output in Cut Release PRs is `updater-test`, because we don't have a way to test this fully automated, we have a semi-automated process. Download updater-test zip file, install the app, run it, expect an updater prompt to a dummy v0.99.99, install it and check that the app comes back at that version (on both macOS and Windows).
##### Updater-test builds
The other `build-publish-apps` output in Cut Release PRs is `updater-test-{platform}`. As we don't have a way to test this fully automatically, we have a semi-automated process. For macOS, Windows, and Linux, download the corresponding updater-test artifact file, install the app, run it, expect an updater prompt to a dummy v0.255.255, install it and check that the app comes back at that version.
The only difference with these builds is that they point to a different update location on the release bucket, with this dummy v0.255.255 always available. This helps ensuring that the version we release will be able to update to the next one available.
If the prompt doesn't show up, start the app in command line to grab the electron-updater logs. This is likely an issue with the current build that needs addressing (or the updater-test location in the storage bucket).
```
# Windows (PowerShell)
& 'C:\Program Files\Zoo Modeling App\Zoo Modeling App.exe'
# macOS
/Applications/Zoo\ Modeling\ App.app/Contents/MacOS/Zoo\ Modeling\ App
# Linux
./Zoo Modeling App-{version}-{arch}-linux.AppImage
```
#### 4. Merge the Cut Release PR
#### 3. Merge the Cut Release PR
This will kick the `create-release` action, that creates a _Draft_ release out of this Cut Release PR merge after less than a minute, with the new version as title and Cut Release PR as description.
#### 5. Publish the release
#### 4. Publish the release
Head over to https://github.com/KittyCAD/modeling-app/releases, the draft release corresponding to the merged Cut Release PR should show up at the top as _Draft_. Click on it, verify the content, and hit _Publish_.
#### 6. Profit
#### 5. Profit
A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter.
@ -352,16 +319,7 @@ Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testin
```bash
cd src/wasm-lib
KITTYCAD_API_TOKEN=XXX cargo test -- --test-threads=1
```
Where `XXX` is an API token from the production engine (NOT the dev environment).
We recommend using [nextest](https://nexte.st/) to run the Rust tests (its faster and is used in CI). Once installed, run the tests using
```
cd src/wasm-lib
KITTYCAD_API_TOKEN=XXX cargo run nextest
cargo test
```
### Mapping CI CD jobs to local commands

View File

@ -1,10 +1,10 @@
---
title: "angleToMatchLengthX"
excerpt: "Returns the angle to match the given length for x."
excerpt: "Compute the angle (in degrees) in o"
layout: manual
---
Returns the angle to match the given length for x.
Compute the angle (in degrees) in o

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,6 @@ layout: manual
## Table of Contents
* [Types](kcl/types)
* [Modules](kcl/modules)
* [Known Issues](kcl/KNOWN-ISSUES)
* [`abs`](kcl/abs)
* [`acos`](kcl/acos)
@ -20,7 +19,6 @@ layout: manual
* [`angledLineToX`](kcl/angledLineToX)
* [`angledLineToY`](kcl/angledLineToY)
* [`arc`](kcl/arc)
* [`arcTo`](kcl/arcTo)
* [`asin`](kcl/asin)
* [`assert`](kcl/assert)
* [`assertEqual`](kcl/assertEqual)
@ -76,23 +74,17 @@ layout: manual
* [`patternTransform`](kcl/patternTransform)
* [`pi`](kcl/pi)
* [`polar`](kcl/polar)
* [`polygon`](kcl/polygon)
* [`pow`](kcl/pow)
* [`profileStart`](kcl/profileStart)
* [`profileStartX`](kcl/profileStartX)
* [`profileStartY`](kcl/profileStartY)
* [`push`](kcl/push)
* [`reduce`](kcl/reduce)
* [`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)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,59 +0,0 @@
---
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"
```

File diff suppressed because one or more lines are too long

View File

@ -36,7 +36,7 @@ exampleSketch = startSketchOn('XZ')
|> close(%)
|> patternCircular2d({
center: [0, 0],
instances: 13,
repetitions: 12,
arcDegrees: 360,
rotateDuplicates: true
}, %)

View File

@ -35,7 +35,7 @@ example = extrude(-5, exampleSketch)
|> patternCircular3d({
axis: [1, -1, 0],
center: [10, -20, 0],
instances: 11,
repetitions: 10,
arcDegrees: 360,
rotateDuplicates: true
}, %)

View File

@ -32,7 +32,7 @@ exampleSketch = startSketchOn('XZ')
|> circle({ center: [0, 0], radius: 1 }, %)
|> patternLinear2d({
axis: [1, 0],
instances: 7,
repetitions: 6,
distance: 4
}, %)

View File

@ -38,7 +38,7 @@ exampleSketch = startSketchOn('XZ')
example = extrude(1, exampleSketch)
|> patternLinear3d({
axis: [1, 0, 1],
instances: 7,
repetitions: 6,
distance: 6
}, %)
```

View File

@ -96,24 +96,24 @@ fn cube = (length, center) => {
p3 = [l + x, -l + y]
return startSketchAt(p0)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
}
width = 20
fn transform = (i) => {
return {
// Move down each time.
translate: [0, 0, -i * width],
// Make the cube longer, wider and flatter each time.
scale: [pow(1.1, i), pow(1.1, i), pow(0.9, i)],
// Turn by 15 degrees each time.
rotation: { angle: 15 * i, origin: "local" }
}
// Move down each time.
translate: [0, 0, -i * width],
// Make the cube longer, wider and flatter each time.
scale: [pow(1.1, i), pow(1.1, i), pow(0.9, i)],
// Turn by 15 degrees each time.
rotation: { angle: 15 * i, origin: "local" }
}
}
myCubes = cube(width, [100, 0])
@ -133,25 +133,25 @@ fn cube = (length, center) => {
p3 = [l + x, -l + y]
return startSketchAt(p0)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
}
width = 20
fn transform = (i) => {
return {
translate: [0, 0, -i * width],
rotation: {
angle: 90 * i,
// Rotate around the overall scene's origin.
origin: "global"
}
translate: [0, 0, -i * width],
rotation: {
angle: 90 * i,
// Rotate around the overall scene's origin.
origin: "global"
}
}
}
myCubes = cube(width, [100, 100])
|> patternTransform(4, transform, %)
```
@ -168,16 +168,16 @@ t = 0.005 // taper factor [0-1)
fn transform = (replicaId) => {
scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
return {
translate: [0, 0, replicaId * 10],
scale: [scale, scale, 0]
}
translate: [0, 0, replicaId * 10],
scale: [scale, scale, 0]
}
}
// Each layer is just a pretty thin cylinder.
fn layer = () => {
return startSketchOn("XY")
// or some other plane idk
|> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(h, %)
// or some other plane idk
|> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(h, %)
}
// The vase is 100 layers tall.
// The 100 layers are replica of each other, with a slight transformation applied to each.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
---
title: "ArcToData"
excerpt: "Data to draw a three point arc (arcTo)."
layout: manual
---
Data to draw a three point arc (arcTo).
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `end` |`[number, number]`| End point of the arc. A point in 3D space | No |
| `interior` |`[number, number]`| Interior point of the arc. A point in 3D space | No |

View File

@ -82,78 +82,6 @@ Raise a number to a power.
----
Are two numbers equal?
**enum:** `==`
----
Are two numbers not equal?
**enum:** `!=`
----
Is left greater than right
**enum:** `>`
----
Is left greater than or equal to right
**enum:** `>=`
----
Is left less than right
**enum:** `<`
----
Is left less than or equal to right
**enum:** `<=`
----

View File

@ -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 |
----

View File

@ -18,36 +18,15 @@ layout: manual
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ImportStatement`| | 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 |
----
**Type:** `object`
## Properties
| 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 +42,11 @@ 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 +62,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 |
----

View File

@ -16,7 +16,7 @@ Data for a circular pattern on a 2D sketch.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `instances` |[`Uint`](/docs/kcl/types/Uint)| The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | No |
| `repetitions` |[`Uint`](/docs/kcl/types/Uint)| The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. | No |
| `center` |`[number, number]`| The center about which to make the pattern. This is a 2D vector. | No |
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
| `rotateDuplicates` |`boolean`| Whether or not to rotate the duplicates as they are copied. | No |

View File

@ -16,7 +16,7 @@ Data for a circular pattern on a 3D model.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `instances` |[`Uint`](/docs/kcl/types/Uint)| The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | No |
| `repetitions` |[`Uint`](/docs/kcl/types/Uint)| The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. | No |
| `axis` |`[number, number, number]`| The axis around which to make the pattern. This is a 3D vector. | No |
| `center` |`[number, number, number]`| The center about which to make the pattern. This is a 3D vector. | No |
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |

View File

@ -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 |

View File

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

View File

@ -24,11 +24,11 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `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,32 +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 |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArrayRangeExpression`| | 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 +202,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 +222,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 +243,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 +263,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 +286,8 @@ KCL value for an optional parameter which was not given an argument. (remember,
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `None`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----

View File

@ -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 |

View File

@ -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 |

View File

@ -24,7 +24,7 @@ Autodesk Filmbox (FBX) format
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `format` |enum: `fbx`| | No |
| `type` |enum: `fbx`| | No |
----
@ -40,7 +40,7 @@ Binary glTF 2.0. We refer to this as glTF since that is how our customers refer
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `format` |enum: `gltf`| | No |
| `type` |enum: `gltf`| | No |
----
@ -56,7 +56,7 @@ Wavefront OBJ format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `format` |enum: `obj`| | No |
| `type` |enum: `obj`| | No |
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
@ -74,7 +74,7 @@ The PLY Polygon File Format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `format` |enum: `ply`| | No |
| `type` |enum: `ply`| | No |
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
@ -92,7 +92,7 @@ SolidWorks part (SLDPRT) format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `format` |enum: `sldprt`| | No |
| `type` |enum: `sldprt`| | No |
----
@ -108,7 +108,7 @@ ISO 10303-21 (STEP) format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `format` |enum: `step`| | No |
| `type` |enum: `step`| | No |
----
@ -124,7 +124,7 @@ ST**ereo**L**ithography format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `format` |enum: `stl`| | No |
| `type` |enum: `stl`| | No |
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |

View File

@ -1,24 +0,0 @@
---
title: "ImportItem"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No |
| `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,16 +0,0 @@
---
title: "ItemVisibility"
excerpt: ""
layout: manual
---
**enum:** `default`, `export`

View File

@ -1,16 +0,0 @@
---
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`

View File

@ -1,10 +1,10 @@
---
title: "KclValue"
excerpt: "Any KCL value."
excerpt: "A memory item."
layout: manual
---
Any KCL value.
A memory item.
@ -23,110 +23,8 @@ Any KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `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 |
| `type` |enum: `UserVal`| | No |
| `value` |``| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -161,10 +59,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 |
----
@ -180,9 +78,9 @@ A plane.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
| `type` |enum: `Plane`| | No |
| `id` |`string`| The id of the plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A memory item. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
@ -213,38 +111,6 @@ 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.
@ -317,25 +183,8 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Function`| | No |
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**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 |
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| A memory item. | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| A memory item. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -16,7 +16,7 @@ Data for a linear pattern on a 2D sketch.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `instances` |[`Uint`](/docs/kcl/types/Uint)| The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | No |
| `repetitions` |[`Uint`](/docs/kcl/types/Uint)| The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. | No |
| `distance` |`number`| The distance between each repetition. This can also be referred to as spacing. | No |
| `axis` |`[number, number]`| The axis of the pattern. This is a 2D vector. | No |

View File

@ -16,7 +16,7 @@ Data for a linear pattern on a 3D model.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `instances` |[`Uint`](/docs/kcl/types/Uint)| The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | No |
| `repetitions` |[`Uint`](/docs/kcl/types/Uint)| The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. | No |
| `distance` |`number`| The distance between each repetition. This can also be referred to as spacing. | No |
| `axis` |`[number, number, number]`| The axis of the pattern. | No |

View File

@ -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 |
----

View File

@ -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 |
----

View File

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

View File

@ -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 |

View File

@ -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 |

View File

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

View File

@ -1,27 +0,0 @@
---
title: "Plane"
excerpt: "A plane."
layout: manual
---
A plane.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |`string`| The id of the plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A plane. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -1,10 +1,10 @@
---
title: "PlaneData"
excerpt: "Orientation data that can be used to construct a plane, not a plane in itself."
excerpt: "Data for a plane."
layout: manual
---
Orientation data that can be used to construct a plane, not a plane in itself.
Data for a plane.

View File

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

View File

@ -16,10 +16,10 @@ A KCL program top level, or function body.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `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 |

View File

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

View File

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

View File

@ -22,18 +22,6 @@ Data for start sketch on. You can start a sketch on a plane or an solid.
----
Data for start sketch on. You can start a sketch on a plane or an solid.
[`Plane`](/docs/kcl/types/Plane)
----
Data for start sketch on. You can start a sketch on a plane or an solid.

View File

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

View File

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

View File

@ -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 |

View File

@ -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}, %)
|> xLine(${commonPoints.num1}, %)`)
|> line([${commonPoints.num1}, 0], %)`)
}
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}, %)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${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}, %)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(${commonPoints.num2 * -1}, %)`)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %)
|> line([-${commonPoints.num2}, 0], %)`)
}
// 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}, %)
|> xLine(${commonPoints.num1}, %, $seg01)
|> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(-segLen(seg01), %)`)
|> line([${commonPoints.num1}, 0], %, $seg01)
|> line([0, ${commonPoints.num1 + 0.01}], %)
|> angledLine([180, segLen(seg01)], %)`)
}
test.describe('Basic sketch', () => {

View File

@ -313,45 +313,3 @@ test(
await electronApp.close()
}
)
test(
'external change of file contents are reflected in editor',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const PROJECT_DIR_NAME = 'lee-was-here'
const {
electronApp,
page,
dir: projectsDir,
} = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const aProjectDir = join(dir, PROJECT_DIR_NAME)
await fsp.mkdir(aProjectDir, { recursive: true })
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await test.step('Open the project', async () => {
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
await page.getByText(PROJECT_DIR_NAME).click()
await u.waitForPageLoad()
})
await u.openFilePanel()
await u.openKclCodePanel()
await test.step('Write to file externally and check for changed content', async () => {
const content = 'ha he ho ho ha blap scap be dap'
await fsp.writeFile(
join(projectsDir, PROJECT_DIR_NAME, 'main.kcl'),
content
)
await u.editorTextMatches(content)
})
await electronApp.close()
}
)

View File

@ -1,80 +0,0 @@
import { test, expect } from '@playwright/test'
import { getUtils, setup, tearDown } from './test-utils'
test.beforeEach(async ({ context, page }, testInfo) => {
await setup(context, page, testInfo)
})
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
function countNewlines(input: string): number {
let count = 0
for (const char of input) {
if (char === '\n') {
count++
}
}
return count
}
test.describe('Debug pane', () => {
test('Artifact IDs in the artifact graph are stable across code edits', async ({
page,
context,
}) => {
const code = `sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([1, 1], %)
`
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const tree = page.getByTestId('debug-feature-tree')
const segment = tree.locator('li', {
hasText: 'segIds:',
hasNotText: 'paths:',
})
await test.step('Test setup', async () => {
await u.waitForAuthSkipAppStart()
await u.openKclCodePanel()
await u.openDebugPanel()
// Set the code in the code editor.
await u.codeLocator.click()
await page.keyboard.type(code, { delay: 0 })
// Scroll to the feature tree.
await tree.scrollIntoViewIfNeeded()
// Expand the feature tree.
await tree.getByText('Feature Tree').click()
// Just expanded the details, making the element taller, so scroll again.
await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
})
// Extract the artifact IDs from the debug feature tree.
const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
// The artifact ID should include a UUID.
expect(initialSegmentIds).toMatch(
/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/
)
await test.step('Move cursor to the bottom of the code editor', async () => {
// Focus on the code editor.
await u.codeLocator.click()
// Make sure the cursor is at the end of the code.
const lines = countNewlines(code) + 1
for (let i = 0; i < lines; i++) {
await page.keyboard.press('ArrowDown')
}
})
await test.step('Enter a comment', async () => {
await page.keyboard.type('|> line([2, 2], %)', { delay: 0 })
// Wait for keyboard input debounce and updated artifact graph.
await page.waitForTimeout(1000)
})
const newSegmentIds = await segment.innerText()
// Strip off the closing bracket.
const initialIds = initialSegmentIds.slice(0, initialSegmentIds.length - 1)
expect(newSegmentIds.slice(0, initialIds.length)).toEqual(initialIds)
})
})

View File

@ -62,8 +62,6 @@ test(
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// The open file's name is `main.kcl`, so the export file name should be `main.gltf`
const exportFileName = `main.gltf`
// Click the export button
await exportButton.click()
@ -98,7 +96,7 @@ test(
.poll(
async () => {
try {
const outputGltf = await fsp.readFile(exportFileName)
const outputGltf = await fsp.readFile('output.gltf')
return outputGltf.byteLength
} catch (e) {
return 0
@ -106,10 +104,10 @@ test(
},
{ timeout: 15_000 }
)
.toBeGreaterThan(300_000)
.toBe(431341)
// clean up exported file
await fsp.rm(exportFileName)
// clean up output.gltf
await fsp.rm('output.gltf')
})
})
@ -140,8 +138,6 @@ test(
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// The open file's name is `other.kcl`, so the export file name should be `other.gltf`
const exportFileName = `other.gltf`
// Click the export button
await exportButton.click()
@ -175,7 +171,7 @@ test(
.poll(
async () => {
try {
const outputGltf = await fsp.readFile(exportFileName)
const outputGltf = await fsp.readFile('output.gltf')
return outputGltf.byteLength
} catch (e) {
return 0
@ -183,10 +179,10 @@ test(
},
{ timeout: 15_000 }
)
.toBeGreaterThan(100_000)
.toBe(102040)
// clean up exported file
await fsp.rm(exportFileName)
// clean up output.gltf
await fsp.rm('output.gltf')
})
await electronApp.close()
})

View File

@ -1,16 +1,6 @@
import { test, expect } from '@playwright/test'
import fsp from 'fs/promises'
import { uuidv4 } from 'lib/utils'
import {
darkModeBgColor,
darkModePlaneColorXZ,
executorInputPath,
getUtils,
setup,
setupElectron,
tearDown,
} from './test-utils'
import { join } from 'path'
import { getUtils, setup, tearDown } from './test-utils'
test.beforeEach(async ({ context, page }, testInfo) => {
await setup(context, page, testInfo)
@ -632,18 +622,16 @@ 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 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()
// expect there to be six auto complete options
await expect(page.locator('.cm-completionLabel')).toHaveCount(8)
// this makes sure we can accept a completion with click
await page.getByText('startSketchOn').click()
await page.keyboard.type("'XZ'")
@ -694,9 +682,6 @@ 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 }) => {
@ -989,84 +974,4 @@ test.describe('Editor tests', () => {
|> close(%)
|> extrude(5, %)`)
})
test.fixme(
`Can use the import stdlib function on a local OBJ file`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'cube')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cube.obj'),
join(bracketDir, 'cube.obj')
)
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
},
})
const viewportSize = { width: 1200, height: 500 }
await page.setViewportSize(viewportSize)
// Locators and constants
const u = await getUtils(page)
const projectLink = page.getByRole('link', { name: 'cube' })
const gizmo = page.locator('[aria-label*=gizmo]')
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
const locationToHavColor = async (
position: { x: number; y: number },
color: [number, number, number]
) => {
return u.getGreatestPixDiff(position, color)
}
const notTheOrigin = {
x: viewportSize.width * 0.55,
y: viewportSize.height * 0.3,
}
const origin = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
const errorIndicators = page.locator('.cm-lint-marker-error')
await test.step(`Open the empty file, see the default planes`, async () => {
await projectLink.click()
await u.waitForPageLoad()
await expect
.poll(
async () => locationToHavColor(notTheOrigin, darkModePlaneColorXZ),
{
timeout: 5000,
message: 'XZ plane color is visible',
}
)
.toBeLessThan(15)
})
await test.step(`Write the import function line`, async () => {
await u.codeLocator.fill(`import('cube.obj')`)
await page.waitForTimeout(800)
})
await test.step(`Reset the camera before checking`, async () => {
await u.doAndWaitForCmd(async () => {
await gizmo.click({ button: 'right' })
await resetCameraButton.click()
}, 'zoom_to_fit')
})
await test.step(`Verify that we see the imported geometry and no errors`, async () => {
await expect(errorIndicators).toHaveCount(0)
await expect
.poll(async () => locationToHavColor(origin, darkModePlaneColorXZ), {
timeout: 3000,
message: 'Plane color should not be visible',
})
.toBeGreaterThan(15)
await expect
.poll(async () => locationToHavColor(origin, darkModeBgColor), {
timeout: 3000,
message: 'Background color should not be visible',
})
.toBeGreaterThan(15)
})
await electronApp.close()
}
)
})

View File

@ -3,7 +3,6 @@ import { test, expect } from './fixtures/fixtureSetup'
import * as fsp from 'fs/promises'
import * as fs from 'fs'
import {
createProject,
executorInputPath,
getUtils,
setup,
@ -26,6 +25,10 @@ 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) => {
@ -51,6 +54,7 @@ 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 () => {
@ -66,13 +70,10 @@ 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({ fileName, waitForToastToDisappear: true })
await toolbar.createFile({ wait: true })
// check we're out of sketch mode
await expect(toolbar.exitSketchBtn).not.toBeVisible()
@ -91,10 +92,10 @@ test.describe('integrations tests', () => {
})
await toolbar.editSketch()
await expect(toolbar.exitSketchBtn).toBeVisible()
await toolbar.expectFileTreeState(['main.kcl', fileName])
await toolbar.expectFileTreeState(['main.kcl', 'Untitled.kcl'])
})
await test.step('check sketch mode is exited when opening a different file', async () => {
await toolbar.openFile(fileName, { wait: false })
await toolbar.openFile('untitled.kcl', { wait: false })
// check we're out of sketch mode
await expect(toolbar.exitSketchBtn).not.toBeVisible()
@ -107,21 +108,26 @@ test.describe('when using the file tree to', () => {
const fromFile = 'main.kcl'
const toFile = 'hello.kcl'
test.fixme(
test(
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
{ tag: '@electron' },
async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise()
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } =
await getUtils(tronApp.page, test)
const {
panesOpen,
createAndSelectProject,
pasteCodeInEditor,
renameFile,
editorTextMatches,
} = await getUtils(tronApp.page, test)
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log)
await panesOpen(['files', 'code'])
await createProject({ name: 'project-000', page: tronApp.page })
await createAndSelectProject('project-000')
// File the main.kcl with contents
const kclCube = await fsp.readFile(
@ -130,9 +136,6 @@ test.describe('when using the file tree to', () => {
)
await pasteCodeInEditor(kclCube)
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
await tronApp.page.waitForTimeout(2000)
await renameFile(fromFile, toFile)
await tronApp.page.reload()
@ -155,20 +158,21 @@ test.describe('when using the file tree to', () => {
}
)
test.fixme(
test(
`create many new untitled files they increment their names`,
{ tag: '@electron' },
async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise()
const { panesOpen, createNewFile } = await getUtils(tronApp.page, test)
const { panesOpen, createAndSelectProject, createNewFile } =
await getUtils(tronApp.page, test)
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log)
await panesOpen(['files'])
await createProject({ name: 'project-000', page: tronApp.page })
await createAndSelectProject('project-000')
await createNewFile('')
await createNewFile('')
@ -191,74 +195,57 @@ 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, 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)
)
},
})
async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise()
const {
openKclCodePanel,
openFilePanel,
createAndSelectProject,
pasteCodeInEditor,
createNewFileAndSelect,
renameFile,
selectFile,
editorTextMatches,
waitForPageLoad,
} = await getUtils(tronApp.page, _test)
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 tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log)
await test.step(`Attempt to rename ${secondFile} to ${mainFile}`, async () => {
await renameFile(secondFile, mainFile)
})
await createAndSelectProject('project-000')
await openKclCodePanel()
await openFilePanel()
// File the main.kcl with contents
const kclCube = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cube.kcl',
'utf-8'
)
await pasteCodeInEditor(kclCube)
await test.step(`Postcondition: ${mainFile} still has the original content`, async () => {
await selectFile(mainFile)
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)
await renameFile(kcl2, kcl1)
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {
await selectFile(kcl1)
await editorTextMatches(kclCube)
})
await tronApp.page.waitForTimeout(500)
await test.step(`Postcondition: ${secondFile} still exists with the original content`, async () => {
await selectFile(secondFile)
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
await selectFile(kcl2)
await editorTextMatches(kclCylinder)
})
await tronApp.close()
await tronApp?.close?.()
}
)
@ -268,15 +255,20 @@ test.describe('when using the file tree to', () => {
async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise()
const { panesOpen, pasteCodeInEditor, deleteFile, editorTextMatches } =
await getUtils(tronApp.page, _test)
const {
panesOpen,
createAndSelectProject,
pasteCodeInEditor,
deleteFile,
editorTextMatches,
} = await getUtils(tronApp.page, _test)
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log)
await panesOpen(['files', 'code'])
await createProject({ name: 'project-000', page: tronApp.page })
await createAndSelectProject('project-000')
// File the main.kcl with contents
const kclCube = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cube.kcl',
@ -284,11 +276,11 @@ test.describe('when using the file tree to', () => {
)
await pasteCodeInEditor(kclCube)
const mainFile = 'main.kcl'
const kcl1 = 'main.kcl'
await deleteFile(mainFile)
await deleteFile(kcl1)
await test.step(`Postcondition: ${mainFile} is recreated but has no content`, async () => {
await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => {
await editorTextMatches('')
})
@ -296,7 +288,7 @@ test.describe('when using the file tree to', () => {
}
)
test.fixme(
test(
'loading small file, then large, then back to small',
{
tag: '@electron',
@ -306,6 +298,7 @@ test.describe('when using the file tree to', () => {
const {
panesOpen,
createAndSelectProject,
pasteCodeInEditor,
createNewFile,
openDebugPanel,
@ -317,7 +310,7 @@ test.describe('when using the file tree to', () => {
tronApp.page.on('console', console.log)
await panesOpen(['files', 'code'])
await createProject({ name: 'project-000', page: tronApp.page })
await createAndSelectProject('project-000')
// Create a small file
const kclCube = await fsp.readFile(
@ -721,7 +714,7 @@ _test.describe('Renaming in the file tree', () => {
})
await _test.step('Rename the folder', async () => {
await page.waitForTimeout(1000)
await page.waitForTimeout(60000)
await folderToRename.click({ button: 'right' })
await _expect(renameMenuItem).toBeVisible()
await renameMenuItem.click()
@ -967,357 +960,4 @@ _test.describe('Deleting items from the file pane', () => {
'TODO - delete folder we are in, with no main.kcl',
async () => {}
)
// Copied from tests above.
_test(
`external deletion of project navigates back home`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const TEST_PROJECT_NAME = 'Test Project'
const {
electronApp,
page,
dir: projectsDirName,
} = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, TEST_PROJECT_NAME), { recursive: true })
await fsp.mkdir(join(dir, TEST_PROJECT_NAME, 'folderToDelete'), {
recursive: true,
})
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, TEST_PROJECT_NAME, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(dir, TEST_PROJECT_NAME, 'folderToDelete', 'someFileWithin.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
// Constants and locators
const projectCard = page.getByText(TEST_PROJECT_NAME)
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const folderToDelete = page.getByRole('button', {
name: 'folderToDelete',
})
const fileWithinFolder = page.getByRole('listitem').filter({
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
})
await _test.step(
'Open project and navigate into folderToDelete',
async () => {
await projectCard.click()
await u.waitForPageLoad()
await _expect(projectMenuButton).toContainText('main.kcl')
await u.closeKclCodePanel()
await u.openFilePanel()
await folderToDelete.click()
await _expect(fileWithinFolder).toBeVisible()
await fileWithinFolder.click()
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
}
)
// Point of divergence. Delete the project folder and see if it goes back
// to the home view.
await _test.step(
'Delete projectsDirName/<project-name> externally',
async () => {
await fsp.rm(join(projectsDirName, TEST_PROJECT_NAME), {
recursive: true,
force: true,
})
}
)
await _test.step('Check the app is back on the home view', async () => {
const projectsDirLink = page.getByText('Loaded from')
await _expect(projectsDirLink).toBeVisible()
})
await electronApp.close()
}
)
// Similar to the above
_test(
`external deletion of file in sub-directory updates the file tree and recreates it on code editor typing`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const TEST_PROJECT_NAME = 'Test Project'
const {
electronApp,
page,
dir: projectsDirName,
} = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, TEST_PROJECT_NAME), { recursive: true })
await fsp.mkdir(join(dir, TEST_PROJECT_NAME, 'folderToDelete'), {
recursive: true,
})
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, TEST_PROJECT_NAME, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(dir, TEST_PROJECT_NAME, 'folderToDelete', 'someFileWithin.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
// Constants and locators
const projectCard = page.getByText(TEST_PROJECT_NAME)
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const folderToDelete = page.getByRole('button', {
name: 'folderToDelete',
})
const fileWithinFolder = page.getByRole('listitem').filter({
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
})
await _test.step(
'Open project and navigate into folderToDelete',
async () => {
await projectCard.click()
await u.waitForPageLoad()
await _expect(projectMenuButton).toContainText('main.kcl')
await u.openFilePanel()
await folderToDelete.click()
await _expect(fileWithinFolder).toBeVisible()
await fileWithinFolder.click()
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
}
)
await _test.step(
'Delete projectsDirName/<project-name> externally',
async () => {
await fsp.rm(
join(
projectsDirName,
TEST_PROJECT_NAME,
'folderToDelete',
'someFileWithin.kcl'
)
)
}
)
await _test.step('Check the file is gone in the file tree', async () => {
await _expect(
page.getByTestId('file-pane-scroll-container')
).not.toContainText('someFileWithin.kcl')
})
await _test.step(
'Check the file is back in the file tree after typing in code editor',
async () => {
await u.pasteCodeInEditor('hello = 1')
await _expect(
page.getByTestId('file-pane-scroll-container')
).toContainText('someFileWithin.kcl')
}
)
await electronApp.close()
}
)
})
_test.describe(
'Undo and redo do not keep history when navigating between files',
() => {
_test(
`open a file, change something, open a different file, hitting undo should do nothing`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'other.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectCard = page.getByText('testProject')
const otherFile = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
await _test.step(
'Open project and make a change to the file',
async () => {
await projectCard.click()
await u.waitForPageLoad()
// Get the text in the code locator.
const originalText = await u.codeLocator.innerText()
// Click in the editor and add some new lines.
await u.codeLocator.click()
await page.keyboard.type(`sketch001 = startSketchOn('XY')
some other shit`)
// Ensure the content in the editor changed.
const newContent = await u.codeLocator.innerText()
expect(originalText !== newContent)
}
)
await _test.step('navigate to other.kcl', async () => {
await u.openFilePanel()
await otherFile.click()
await u.waitForPageLoad()
await u.openKclCodePanel()
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
})
await _test.step('hit undo', async () => {
// Get the original content of the file.
const originalText = await u.codeLocator.innerText()
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
})
}
)
_test(
`open a file, change something, undo it, open a different file, hitting redo should do nothing`,
{ tag: '@electron' },
// Skip on windows i think the keybindings are different for redo.
async ({ browserName }, testInfo) => {
test.skip(process.platform === 'win32', 'Skip on windows')
const { page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'other.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectCard = page.getByText('testProject')
const otherFile = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
const badContent = 'this shit'
await _test.step(
'Open project and make a change to the file',
async () => {
await projectCard.click()
await u.waitForPageLoad()
// Get the text in the code locator.
const originalText = await u.codeLocator.innerText()
// Click in the editor and add some new lines.
await u.codeLocator.click()
await page.keyboard.type(badContent)
// Ensure the content in the editor changed.
const newContent = await u.codeLocator.innerText()
expect(originalText !== newContent)
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
// Hit redo.
await page.keyboard.down('Shift')
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).toContainText(badContent)
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
}
)
await _test.step('navigate to other.kcl', async () => {
await u.openFilePanel()
await otherFile.click()
await u.waitForPageLoad()
await u.openKclCodePanel()
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
await expect(u.codeLocator).not.toContainText(badContent)
})
await _test.step('hit redo', async () => {
// Get the original content of the file.
const originalText = await u.codeLocator.innerText()
// Now hit redo
await page.keyboard.down('Shift')
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
})
}
)
}
)

View File

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

View File

@ -20,7 +20,6 @@ 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
@ -37,7 +36,7 @@ export class AuthenticatedApp {
;(window as any).playwrightSkipFilePicker = true
}, code)
await this.page.setViewportSize(this.viewPortSize)
await this.page.setViewportSize({ width: 1000, height: 500 })
await u.waitForAuthSkipAppStart()
}

View File

@ -10,28 +10,8 @@ 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 = {
camera: {
position: [number, number, number]
target: [number, number, number]
}
}
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
@ -42,22 +22,6 @@ export class SceneFixture {
this.page = page
this.reConstruct(page)
}
private _serialiseScene = async (): Promise<SceneSerialised> => {
const camera = await this.getCameraInfo()
return {
camera,
}
}
expectState = async (expected: SceneSerialised) => {
return expect
.poll(() => this._serialiseScene(), {
message: `Expected scene state to match`,
})
.toEqual(expected)
}
reConstruct = (page: Page) => {
this.page = page
@ -67,8 +31,8 @@ export class SceneFixture {
makeMouseHelpers = (
x: number,
y: number,
{ steps }: { steps: number } = { steps: 20 }
): [ClickHandler, MoveHandler] =>
{ steps }: { steps: number } = { steps: 5000 }
) =>
[
(clickParams?: mouseParams) => {
if (clickParams?.pixelDiff) {
@ -91,47 +55,6 @@ 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.
*
@ -164,38 +87,8 @@ export class SceneFixture {
)
await closeDebugPanel(this.page)
}
/** Forces a refresh of the camera position and target displayed
* in the debug panel and then returns the values of the fields
*/
async getCameraInfo() {
await openAndClearDebugPanel(this.page)
await sendCustomCmd(this.page, {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await this.waitForExecutionDone()
const position = await Promise.all([
this.page.getByTestId('cam-x-position').inputValue().then(Number),
this.page.getByTestId('cam-y-position').inputValue().then(Number),
this.page.getByTestId('cam-z-position').inputValue().then(Number),
])
const target = await Promise.all([
this.page.getByTestId('cam-x-target').inputValue().then(Number),
this.page.getByTestId('cam-y-target').inputValue().then(Number),
this.page.getByTestId('cam-z-target').inputValue().then(Number),
])
await closeDebugPanel(this.page)
return {
position,
target,
}
}
waitForExecutionDone = async () => {
await expect(this.exeIndicator).toBeVisible({ timeout: 30000 })
await expect(this.exeIndicator).toBeVisible()
}
expectPixelColor = async (
@ -221,17 +114,4 @@ export class SceneFixture {
)
})
}
get gizmo() {
return this.page.locator('[aria-label*=gizmo]')
}
async clickGizmoMenuItem(name: string) {
await this.gizmo.click({ button: 'right' })
const buttonToTest = this.page.getByRole('button', {
name: name,
})
await expect(buttonToTest).toBeVisible()
await buttonToTest.click()
}
}

View File

@ -7,7 +7,6 @@ export class ToolbarFixture {
extrudeButton!: Locator
startSketchBtn!: Locator
lineBtn!: Locator
rectangleBtn!: Locator
exitSketchBtn!: Locator
editSketchBtn!: Locator
@ -16,7 +15,6 @@ export class ToolbarFixture {
fileCreateToast!: Locator
filePane!: Locator
exeIndicator!: Locator
treeInputField!: Locator
constructor(page: Page) {
this.page = page
@ -26,13 +24,11 @@ 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')
@ -61,15 +57,10 @@ export class ToolbarFixture {
expectFileTreeState = async (expected: string[]) => {
await expect.poll(this._serialiseFileTree).toEqual(expected)
}
createFile = async (args: {
fileName: string
waitForToastToDisappear: boolean
}) => {
createFile = async ({ wait }: { wait: boolean } = { wait: false }) => {
await this.createFileBtn.click()
await this.treeInputField.fill(args.fileName)
await this.treeInputField.press('Enter')
await expect(this.fileCreateToast).toBeVisible()
if (args.waitForToastToDisappear) {
if (wait) {
await this.fileCreateToast.waitFor({ state: 'detached' })
}
}

View File

@ -18,7 +18,7 @@ export const isErrorWhitelisted = (exception: Error) => {
{
name: '"{"kind"',
message:
'"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"',
'"engine","sourceRanges":[[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,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)\`"}
'{"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)\`"}
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,0]],"msg":"Failed to wait for promise from engine: JsValue(\\"Force interrupt, executionIsStale, new AST requested\\")"}`,
message: `engine","sourceRanges":[[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',
},

View File

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

View File

@ -451,103 +451,3 @@ 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
)
})
})

View File

@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test'
import { test, expect, Page } from '@playwright/test'
import {
doExport,
executorInputPath,
@ -7,7 +7,7 @@ import {
Paths,
setupElectron,
tearDown,
createProject,
createProjectAndRenameIt,
} from './test-utils'
import fsp from 'fs/promises'
import fs from 'fs'
@ -247,7 +247,7 @@ test.describe('Can export from electron app', () => {
.poll(
async () => {
try {
const outputGltf = await fsp.readFile('main.gltf')
const outputGltf = await fsp.readFile('output.gltf')
return outputGltf.byteLength
} catch (e) {
return 0
@ -255,10 +255,10 @@ test.describe('Can export from electron app', () => {
},
{ timeout: 15_000 }
)
.toBeGreaterThan(300_000)
.toBe(431341)
// clean up exported file
await fsp.rm('main.gltf')
// clean up output.gltf
await fsp.rm('output.gltf')
})
await electronApp.close()
@ -503,261 +503,21 @@ test(
}
)
test.describe(`Project management commands`, () => {
test(
`Rename from project page`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const projectName = `my_project_to_rename`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
const u = await getUtils(page)
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `project-000`
// const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const commandContinueButton = page.getByRole('button', {
name: 'Continue',
})
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully renamed`)
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await projectHomeLink.click()
await u.waitForPageLoad()
})
await test.step(`Run rename command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandContinueButton).toBeVisible()
await commandContinueButton.click()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
// TODO: in future I'd like the behavior to be to
// navigate to the new project's page directly,
// see ProjectContextProvider.tsx:158
await test.step(`Check the project was renamed and we navigated home`, async () => {
await expect(projectHomeLink.first()).toBeVisible()
await expect(projectHomeLink.first()).toContainText(projectRenamedName)
})
await electronApp.close()
}
)
test(
`Delete from project page`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const projectName = `my_project_to_delete`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
const u = await getUtils(page)
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'delete project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully deleted`)
const noProjectsMessage = page.getByText('No Projects found')
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await projectHomeLink.click()
await u.waitForPageLoad()
})
await test.step(`Run delete command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandWarning).toBeVisible()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
await test.step(`Check the project was deleted and we navigated home`, async () => {
await expect(noProjectsMessage).toBeVisible()
})
await electronApp.close()
}
)
test(
`Rename from home page`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const projectName = `my_project_to_rename`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `project-000`
const commandContinueButton = page.getByRole('button', {
name: 'Continue',
})
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully renamed`)
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await expect(projectHomeLink).toBeVisible()
})
await test.step(`Run rename command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandContinueButton).toBeVisible()
await commandContinueButton.click()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
await test.step(`Check the project was renamed`, async () => {
await expect(
page.getByRole('link', { name: projectRenamedName })
).toBeVisible()
await expect(projectHomeLink).not.toHaveText(projectName)
})
await electronApp.close()
}
)
test(
`Delete from home page`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const projectName = `my_project_to_delete`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'delete project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully deleted`)
const noProjectsMessage = page.getByText('No Projects found')
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await expect(projectHomeLink).toBeVisible()
})
await test.step(`Run delete command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandWarning).toBeVisible()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
await test.step(`Check the project was deleted`, async () => {
await expect(projectHomeLink).not.toBeVisible()
await expect(noProjectsMessage).toBeVisible()
})
await electronApp.close()
}
)
})
test(
'File in the file pane should open with a single click',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const projectName = 'router-template-slate'
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
`${dir}/router-template-slate/main.kcl`
)
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/${projectName}/otherThingToClickOn.kcl`
`${dir}/router-template-slate/otherThingToClickOn.kcl`
)
},
})
@ -766,7 +526,7 @@ test(
page.on('console', console.log)
await page.getByText(projectName).click()
await page.getByText('router-template-slate').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
@ -854,36 +614,37 @@ test(
}
)
test.fixme(
test(
'Deleting projects, can delete individual project, can still create projects after deleting all',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const projectData = [
['router-template-slate', 'cylinder.kcl'],
['bracket', 'focusrite_scarlett_mounting_braket.kcl'],
['lego', 'lego.kcl'],
]
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
// Do these serially to ensure the order is correct
for (const [name, file] of projectData) {
await fsp.mkdir(join(dir, name), { recursive: true })
await fsp.copyFile(
executorInputPath(file),
join(dir, name, `main.kcl`)
)
// Wait 1s between each project to ensure the order is correct
await new Promise((r) => setTimeout(r, 1_000))
}
},
})
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
const createProjectAndRenameItTest = async ({
name,
page,
}: {
name: string
page: Page
}) => {
await test.step(`Create and rename project ${name}`, async () => {
await createProjectAndRenameIt({ name, page })
})
}
// we need to create the folders so that the order is correct
// creating them ahead of time with fs tools means they all have the same timestamp
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
await createProjectAndRenameItTest({ name: 'bracket', page })
await createProjectAndRenameItTest({ name: 'lego', page })
await test.step('delete the middle project, i.e. the bracket project', async () => {
const project = page.getByTestId('project-link').getByText('bracket')
const project = page.getByText('bracket')
await project.hover()
await project.focus()
@ -927,10 +688,10 @@ test.fixme(
})
await test.step('Check we can still create a project', async () => {
await createProject({ name: 'project-000', page, returnHome: true })
await expect(
page.getByTestId('project-link').filter({ hasText: 'project-000' })
).toBeVisible()
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText('project-000')).toBeVisible()
})
await electronApp.close()
@ -983,26 +744,8 @@ test(
'Can sort projects on home page',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const projectData = [
['router-template-slate', 'cylinder.kcl'],
['bracket', 'focusrite_scarlett_mounting_braket.kcl'],
['lego', 'lego.kcl'],
]
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
// Do these serially to ensure the order is correct
for (const [name, file] of projectData) {
await fsp.mkdir(join(dir, name), { recursive: true })
await fsp.copyFile(
executorInputPath(file),
join(dir, name, `main.kcl`)
)
// Wait 1s between each project to ensure the order is correct
await new Promise((r) => setTimeout(r, 1_000))
}
},
})
await page.setViewportSize({ width: 1200, height: 500 })
@ -1010,6 +753,24 @@ test(
page.on('console', console.log)
const createProjectAndRenameItTest = async ({
name,
page,
}: {
name: string
page: Page
}) => {
await test.step(`Create and rename project ${name}`, async () => {
await createProjectAndRenameIt({ name, page })
})
}
// we need to create the folders so that the order is correct
// creating them ahead of time with fs tools means they all have the same timestamp
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
await createProjectAndRenameItTest({ name: 'bracket', page })
await createProjectAndRenameItTest({ name: 'lego', page })
await test.step('should be shorted by modified initially', async () => {
const lastModifiedButton = page.getByRole('button', {
name: 'Last Modified',
@ -1091,7 +852,7 @@ test(
}
)
test.fixme(
test(
'When the project folder is empty, user can create new project and open it.',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
@ -1101,24 +862,28 @@ test.fixme(
page.on('console', console.log)
// Locators and constants
const gizmo = page.locator('[aria-label*=gizmo]')
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
const pointOnModel = { x: 660, y: 250 }
const expectedStartCamZPosition = 15633.47
// Constants and locators
const projectLinks = page.getByTestId('project-link')
// expect to see text "No Projects found"
await expect(page.getByText('No Projects found')).toBeVisible()
await createProject({ name: 'project-000', page, returnHome: true })
await expect(projectLinks.getByText('project-000')).toBeVisible()
await page.getByRole('button', { name: 'New project' }).click()
await projectLinks.getByText('project-000').click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await u.waitForPageLoad()
await expect(page.getByText('project-000')).toBeVisible()
await page.getByText('project-000').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
await page.locator('.cm-content').fill(`sketch001 = startSketchOn('XZ')
|> startProfileAt([-87.4, 282.92], %)
@ -1128,28 +893,8 @@ test.fixme(
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(200, sketch001)`)
await page.waitForTimeout(800)
async function getCameraZValue() {
return page
.getByTestId('cam-z-position')
.inputValue()
.then((value) => parseFloat(value))
}
await test.step(`Reset camera`, async () => {
await u.openDebugPanel()
await u.clearCommandLogs()
await u.doAndWaitForCmd(async () => {
await gizmo.click({ button: 'right' })
await resetCameraButton.click()
}, 'zoom_to_fit')
await expect
.poll(getCameraZValue, {
message: 'Camera Z should be at expected position after reset',
})
.toEqual(expectedStartCamZPosition)
})
const pointOnModel = { x: 660, y: 250 }
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
@ -1157,7 +902,7 @@ extrude001 = extrude(200, sketch001)`)
.poll(() => u.getGreatestPixDiff(pointOnModel, [143, 143, 143]), {
timeout: 10_000,
})
.toBeLessThan(30)
.toBeLessThan(15)
await expect(async () => {
await page.mouse.move(0, 0, { steps: 5 })
@ -1175,10 +920,16 @@ extrude001 = extrude(200, sketch001)`)
page.getByRole('button', { name: 'New project' })
).toBeVisible()
const createProject = async (projectNum: number) => {
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
const projectNumStr = projectNum.toString().padStart(3, '0')
await expect(page.getByText(`project-${projectNumStr}`)).toBeVisible()
}
for (let i = 1; i <= 10; i++) {
const name = `project-${i.toString().padStart(3, '0')}`
await createProject({ name, page, returnHome: true })
await expect(projectLinks.getByText(name)).toBeVisible()
await createProject(i)
}
await electronApp.close()
}
@ -1353,10 +1104,11 @@ test(
await page.getByTestId('settings-close-button').click()
await expect(page.getByText('No Projects found')).toBeVisible()
await createProject({ name: 'project-000', page, returnHome: true })
await expect(
page.getByTestId('project-link').filter({ hasText: 'project-000' })
).toBeVisible()
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText(`project-000`)).toBeVisible()
})
await test.step('We can change back to the original root project directory', async () => {
@ -1490,13 +1242,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,8 +1421,7 @@ test(
}
)
// Flaky
test.fixme(
test(
'Original project name persist after onboarding',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
@ -1683,7 +1434,7 @@ test.fixme(
page.on('console', console.log)
await test.step('Should create and name a project called wrist brace', async () => {
await createProject({ name: 'wrist brace', page, returnHome: true })
await createProjectAndRenameIt({ name: 'wrist brace', page })
})
await test.step('Should go through onboarding', async () => {

View File

@ -115,7 +115,7 @@ test.describe('Sketch tests', () => {
'persistCode',
`sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -14.01], %)
|> xLine(12.73, %)
|> line([12.73, -0.09], %)
|> 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], %)
|> yLine(12.34, %)
|> line([-12.34, 12.34], %)
`)
}).toPass({ timeout: 40_000, intervals: [1_000] })
@ -637,6 +637,7 @@ 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)
@ -645,7 +646,7 @@ test.describe('Sketch tests', () => {
await u.openDebugPanel()
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
const { toSU, toU, click00r } = getMovementUtils({ center, page })
const { toSU, click00r } = getMovementUtils({ center, page })
await expect(
page.getByRole('button', { name: 'Start Sketch' })
@ -674,15 +675,15 @@ test.describe('Sketch tests', () => {
await click00r(50, 0)
await page.waitForTimeout(100)
codeStr += ` |> xLine(${toU(50, 0)[0]}, %)`
codeStr += ` |> line(${toSU([50, 0])}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 50)
codeStr += ` |> yLine(${toU(0, 50)[1]}, %)`
codeStr += ` |> line(${toSU([0, 50])}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(-50, 0)
codeStr += ` |> xLine(${toU(-50, 0)[0]}, %)`
codeStr += ` |> line(${toSU([-50, 0])}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
// exit the sketch, reset relative clicker
@ -708,18 +709,16 @@ test.describe('Sketch tests', () => {
codeStr += ` |> startProfileAt([2.03, 0], %)`
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 += ` |> xLine(2.04, %)`
codeStr += ` |> line([2.04, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 30)
codeStr += ` |> yLine(-2.03, %)`
codeStr += ` |> line([0, -2.03], %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(-30, 0)
codeStr += ` |> xLine(-2.04, %)`
codeStr += ` |> line([-2.04, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(undefined, undefined)
@ -743,8 +742,8 @@ test.describe('Sketch tests', () => {
const code = `sketch001 = startSketchOn('-XZ')
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|> xLine(${roundOff(scale * 139.19)}, %)
|> yLine(-${roundOff(scale * 139.2)}, %)
|> line([${roundOff(scale * 139.19)}, 0], %)
|> line([0, -${roundOff(scale * 139.2)}], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`
@ -1274,44 +1273,3 @@ test2.describe('Sketch mode should be toleratant to syntax errors', () => {
}
)
})
test2.describe(`Sketching with offset planes`, () => {
test2(
`Can select an offset plane to sketch on`,
async ({ app, scene, toolbar, editor }) => {
// We seed the scene with a single offset plane
await app.initialise(`offsetPlane001 = offsetPlane("XY", 10)`)
const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200)
await test2.step(`Start sketching on the offset plane`, async () => {
await toolbar.startSketchPlaneSelection()
await test2.step(`Hovering should highlight code`, async () => {
await planeHover()
await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
diagnostics: [],
highlightedCode: 'offsetPlane("XY", 10)',
})
})
await test2.step(
`Clicking should select the plane and enter sketch mode`,
async () => {
await planeClick()
// Have to wait for engine-side animation to finish
await app.page.waitForTimeout(600)
await expect2(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
diagnostics: [],
highlightedCode: '',
})
}
)
})
}
)
})

View File

@ -283,7 +283,7 @@ part001 = startSketchOn('-XZ')
const gltfFilename = filenames.filter((t: string) =>
t.includes('.gltf')
)[0]
if (!gltfFilename) throw new Error('No gLTF in this archive')
if (!gltfFilename) throw new Error('No output.gltf in this archive')
cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${parentPath}/${gltfFilename} ${imagePath}`
}
@ -462,7 +462,7 @@ test(
await page.waitForTimeout(100)
code += `
|> xLine(7.25, %)`
|> line([7.25, 0], %)`
await expect(page.locator('.cm-content')).toHaveText(code)
await page
@ -471,7 +471,7 @@ test(
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
await page.waitForTimeout(1000)
await page.waitForTimeout(300)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
@ -521,6 +521,7 @@ test(
const startXPx = 600
// Equip the rectangle tool
await page.getByRole('button', { name: 'line Line', exact: true }).click()
await page
.getByRole('button', { name: 'rectangle Corner rectangle', exact: true })
.click()
@ -528,7 +529,6 @@ test(
// Draw the rectangle
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 30)
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 })
await page.waitForTimeout(800)
// Ensure the draft rectangle looks the same as it usually does
await expect(page).toHaveScreenshot({
@ -647,7 +647,7 @@ test.describe(
await page.waitForTimeout(100)
code += `
|> xLine(7.25, %)`
|> line([7.25, 0], %)`
await expect(u.codeLocator).toHaveText(code)
await page
@ -670,7 +670,6 @@ test.describe(
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
})
// exit sketch
@ -688,7 +687,6 @@ test.describe(
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
})
})
@ -752,7 +750,7 @@ test.describe(
await page.waitForTimeout(100)
code += `
|> xLine(184.3, %)`
|> line([184.3, 0], %)`
await expect(u.codeLocator).toHaveText(code)
await page
@ -896,7 +894,7 @@ test(
// Wait for the second extrusion to appear
// TODO: Find a way to truly know that the objects have finished
// rendering, because an execution-done message is not sufficient.
await page.waitForTimeout(2000)
await page.waitForTimeout(1000)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
@ -940,7 +938,7 @@ test(
// Wait for the second extrusion to appear
// TODO: Find a way to truly know that the objects have finished
// rendering, because an execution-done message is not sufficient.
await page.waitForTimeout(2000)
await page.waitForTimeout(1000)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
@ -1031,7 +1029,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
})
})
test.fixme('theme persists', async ({ page, context }) => {
test('theme persists', async ({ page, context }) => {
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

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