Compare commits

..

54 Commits

Author SHA1 Message Date
2204575466 Fix to not blow away files with comments 2025-03-21 16:54:19 -04:00
65f9bcc4ea Fix so that only comments doesn't format to empty 2025-03-21 16:54:19 -04:00
ced2072768 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 20:40:04 +00:00
5e1fbccaec A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 20:25:24 +00:00
658700b533 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 20:01:12 +00:00
670d95e692 Merge branch 'main' into jtran/units-indicator 2025-03-21 15:46:42 -04:00
744bb254e9 Fix yarn lint 2025-03-21 12:59:13 -04:00
b9a61c83d6 Possibly the last test that needs updated with an inline unit annotation 2025-03-21 12:41:11 -04:00
b5c25fe9e7 More test updates to use inline in setting 2025-03-21 12:32:45 -04:00
a3b6da03d3 Undo kclSamplesInputPath approach.
Instead, just add a second reliable executor input file to use.
2025-03-21 12:13:19 -04:00
e6c060c410 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 16:00:35 +00:00
09dabd8fc2 I'm pretty sure our lighting is very affected by scale 2025-03-21 11:46:26 -04:00
dfac82d2aa Merge branch 'main' into jtran/units-indicator 2025-03-21 11:42:58 -04:00
cb32881cf2 Fix up more tests with setting annotations 2025-03-21 11:42:42 -04:00
a01498bf33 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 15:15:40 +00:00
d1d3caad5c A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 15:02:02 +00:00
c62e0bfd64 Fix up point-and-click tests with unit setting lines 2025-03-21 10:48:01 -04:00
256b6b33f3 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 14:37:22 +00:00
35c80a78bb Merge branch 'main' into jtran/units-indicator 2025-03-21 10:23:42 -04:00
a5e682f9b0 Fix to reset to the default units when clearing the scene 2025-03-20 19:16:15 -04:00
c1319ca980 Add cache test 2025-03-20 19:16:15 -04:00
6e8fcdc088 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 22:52:01 +00:00
273b3b59e2 Merge branch 'main' into jtran/units-indicator 2025-03-20 18:38:37 -04:00
bac7ae4ff1 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 22:03:24 +00:00
9f5c2512a7 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 21:48:58 +00:00
485c5ab455 Fix chamfer e2e tests 2025-03-20 17:34:30 -04:00
d0cba2f080 Update output after new keyboard sample 2025-03-20 17:34:30 -04:00
7f1ed3b7cc A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
9e73987796 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
01e1589cd6 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
4b1901db7e A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
a892699b65 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
76dbb4a5d3 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
d6cb471791 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:29 -04:00
ac4d68a812 Fix snapshot test to use mask 2025-03-20 17:34:12 -04:00
423ab5169f Add new assertion when creating project 2025-03-20 17:34:12 -04:00
6f013ec5fe Revert "Add arrow down to try to fix tests"
This reverts commit cde90b0e058e9fd4d4b68087c971195d3843d104.
2025-03-20 17:34:12 -04:00
8c3fc51d28 Change signature to reduce wasm round-trips 2025-03-20 17:34:12 -04:00
cffb777a7b Fix lint 2025-03-20 17:34:12 -04:00
0409b3159c Fix to consider only @settings to not be worth preserving 2025-03-20 17:34:12 -04:00
27c2c50508 Fix more tests 2025-03-20 17:34:12 -04:00
8f5eb9266b Fix formatting 2025-03-20 17:34:12 -04:00
041000fbb6 Trying to fix test assertions 2025-03-20 17:34:12 -04:00
15c3f21acc Add arrow down to try to fix tests 2025-03-20 17:34:12 -04:00
626dbf46f8 Fix tests 2025-03-20 17:34:12 -04:00
dc3a17149d Regenerate derive-docs 2025-03-20 17:34:11 -04:00
98238d040b Update output after setting units differently 2025-03-20 17:34:11 -04:00
3dff5b1c30 Remove units from settings struct 2025-03-20 17:34:11 -04:00
2f362e1774 Change tolerance to use the module units 2025-03-20 17:34:11 -04:00
fd45574652 Fix loading samples 2025-03-20 17:34:11 -04:00
1e23f37287 Change so that the new file in new projects get the units 2025-03-20 17:34:11 -04:00
8936a885c3 Move definition so that all units stuff is together and no cyclic imports 2025-03-20 17:34:09 -04:00
e94bdeb2ce Change so that new KCL files respect the length unit setting 2025-03-20 17:33:37 -04:00
494d8bdf4c Change lower-right controls units menu to be tied to only the per-file units 2025-03-20 17:33:36 -04:00
1180 changed files with 103636 additions and 212979 deletions

View File

@ -1,6 +1,5 @@
NODE_ENV=development
DEV=true
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
@ -9,5 +8,3 @@ VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
#VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
FAIL_ON_CONSOLE_ERRORS=true

12
.envrc
View File

@ -1,13 +1 @@
# Load optional shared environment variables
source_up_if_exists
# Load default development environment variables
dotenv .env.development
# Load optional environment variables overrides
dotenv_if_exists .env.development.local
# Load optional testing environment variables
dotenv_if_exists e2e/playwright/playwright-secrets.env
use flake .

78
.eslintrc Normal file
View File

@ -0,0 +1,78 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": [
"react-perf",
"css-modules",
"jest",
"jsx-a11y",
"react",
"react-hooks",
"suggest-no-throw",
"testing-library",
"@typescript-eslint"
],
"extends": [
"plugin:css-modules/recommended",
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended"
],
"rules": {
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-unused-vars": ["error", {
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true,
"vars": "all",
"args": "none"
}],
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"no-restricted-globals": [
"error",
{
"name": "isNaN",
"message": "Use Number.isNaN() instead."
},
],
"no-restricted-syntax": [
"error",
{
"selector": "CallExpression[callee.object.name='Array'][callee.property.name='isArray']",
"message": "Use isArray() in lib/utils.ts instead of Array.isArray()."
}
],
"semi": [
"error",
"never"
],
"react-hooks/exhaustive-deps": "off",
"suggest-no-throw/suggest-no-throw": "warn",
},
"overrides": [
{
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
"extends": [
"plugin:testing-library/react"
],
"rules": {
"suggest-no-throw/suggest-no-throw": "off",
"testing-library/prefer-screen-queries": "off",
"jest/valid-expect": "off"
}
},
{
"files": ["src/**/*.test.ts"],
"extends": [
"plugin:testing-library/react"
],
"rules": {
"suggest-no-throw/suggest-no-throw": "off",
}
}
]
}

View File

@ -1,127 +0,0 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": [
"react-perf",
"css-modules",
"jest",
"jsx-a11y",
"react",
"react-hooks",
"suggest-no-throw",
"testing-library",
"@typescript-eslint"
],
"extends": [
"plugin:css-modules/recommended",
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended"
],
"rules": {
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-array-delete": "error",
"@typescript-eslint/no-duplicate-enum-values": "error",
"@typescript-eslint/no-duplicate-type-constituents": "error",
"@typescript-eslint/no-empty-object-type": "error",
"@typescript-eslint/no-extra-non-null-assertion": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-for-in-array": "error",
"no-implied-eval": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-implied-eval": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
"@typescript-eslint/no-redundant-type-constituents": "error",
"@typescript-eslint/no-this-alias": "warn",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unnecessary-type-constraint": "error",
"no-unused-vars": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-unused-vars": [
"error",
{
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true,
"vars": "all",
"args": "none"
}
],
"@typescript-eslint/no-unsafe-unary-minus": "error",
"@typescript-eslint/no-wrapper-object-types": "error",
"no-throw-literal": "off", // Use @typescript-eslint/only-throw-error instead.
"@typescript-eslint/only-throw-error": "error",
"@typescript-eslint/prefer-as-const": "warn",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"no-restricted-globals": [
"error",
{
"name": "isNaN",
"message": "Use Number.isNaN() instead."
}
],
"no-restricted-syntax": [
"error",
{
"selector": "CallExpression[callee.object.name='Array'][callee.property.name='isArray']",
"message": "Use isArray() in lib/utils.ts instead of Array.isArray()."
},
{
"selector": "CallExpression[callee.object.name='TOML'][callee.property.name='stringify']",
"message": "Do not use TOML.stringify directly. Use the wrappers in test-utils instead like settingsToToml."
},
{
"selector": "CallExpression[callee.object.name='TOML'][callee.property.name='parse']",
"message": "Do not use TOML.parse directly. Use the wrappers in test-utils instead like tomlToSettings."
}
],
"no-restricted-imports": [
"error",
{
"patterns": [
// Restrict all relative imports except for .css files.
{
"group": ["./*", "../*", "!./*.css", "!../*.css"],
"message": "Use absolute imports instead."
}
]
}
],
"semi": ["error", "never"],
"react-hooks/exhaustive-deps": "off",
"suggest-no-throw/suggest-no-throw": "error"
},
"overrides": [
{
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
"extends": ["plugin:testing-library/react"],
"rules": {
"suggest-no-throw/suggest-no-throw": "off",
"testing-library/prefer-screen-queries": "off",
"jest/valid-expect": "off"
}
},
{
"files": ["src/**/*.test.ts"],
"extends": ["plugin:testing-library/react"],
"rules": {
"suggest-no-throw/suggest-no-throw": "off"
}
},
{
"files": ["packages/**/*.ts", "rust/**/*.ts"],
"extends": [],
"rules": {
"no-restricted-imports": "off"
}
}
]
}

View File

@ -1,5 +1,5 @@
name: Bug Report
description: File a bug report for the Zoo Design Studio
description: File a bug report for the Zoo Modeling App
title: "[BUG]: "
labels: ["bug"]
assignees: []
@ -70,7 +70,7 @@ body:
id: version
attributes:
label: Version
description: "The version of the Zoo Design Studio you're using."
description: "The version of the Zoo Modeling App you're using."
placeholder: "example: v0.15.0. You can find this in the settings."
validations:
required: true

View File

@ -241,7 +241,7 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: out-arm64-${{ matrix.platform }}
# first two will pick both Zoo Design Studio-$VERSION-arm64-win.exe and Zoo Design Studio-$VERSION-win.exe
# 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.*

View File

@ -1,9 +1,7 @@
name: E2E Tests
on:
push:
branches:
- main
- all-e2e # this bypasses `fixme()` using `orRunWhenFullSuiteEnabled()`
branches: [ main ]
pull_request:
schedule:
- cron: 0 * * * * # hourly
@ -17,6 +15,7 @@ permissions:
pull-requests: write
actions: read
jobs:
conditions:
@ -68,16 +67,14 @@ jobs:
- name: Display conditions
shell: bash
run: |
# For debugging purposes
# For debugging purposes.
set -euo pipefail
echo "GITHUB_REF: $GITHUB_REF"
echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF"
echo "GITHUB_BASE_REF: $GITHUB_BASE_REF"
echo "significant: ${{ steps.path-changes.outputs.significant }}"
echo "should-run: ${{ steps.should-run.outputs.should-run }}"
prepare-wasm:
# separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
# seperate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: conditions
steps:
@ -162,6 +159,7 @@ jobs:
name: prepared-wasm
path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
snapshots:
name: playwright:snapshots:ubuntu
@ -229,6 +227,10 @@ jobs:
timeout_minutes: 30
max_attempts: 3
env:
CI: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
@ -241,7 +243,7 @@ jobs:
retention-days: 30
overwrite: true
- name: Check for changes
- name: check for changes
if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
@ -253,8 +255,7 @@ jobs:
fi
- name: Commit changes, if any
# TODO: find a more reliable way to detect visual changes
if: ${{ false && needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
@ -281,7 +282,7 @@ jobs:
os:
- "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
- namespace-profile-macos-8-cores
- windows-latest-8-cores
- windows-latest
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
# Disable macos and windows tests on hourly e2e tests since we only care
@ -292,7 +293,7 @@ jobs:
exclude:
- os: namespace-profile-macos-8-cores
isScheduled: true
- os: windows-latest-8-cores
- os: windows-latest
isScheduled: true
# TODO: add ref here for main and latest release tag
runs-on: ${{ matrix.os }}
@ -340,7 +341,7 @@ jobs:
run: yarn tronb:vite:dev
- name: Install vector
if: ${{ needs.conditions.outputs.should-run == 'true' && contains(matrix.os, 'ubuntu') }}
if: contains(matrix.os, 'ubuntu')
shell: bash
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
@ -370,10 +371,14 @@ jobs:
with:
shell: bash
command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{ env.OS_NAME }}
timeout_minutes: 30
max_attempts: 9
timeout_minutes: 45
max_attempts: 15
env:
CI: true
FAIL_ON_CONSOLE_ERRORS: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- uses: actions/upload-artifact@v4

View File

@ -28,87 +28,43 @@ jobs:
- run: yarn fmt-check
yarn-build-wasm:
# Build the wasm blob once on the fastest runner.
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install
- name: Use correct Rust toolchain
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- run: yarn install
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
cache: false # Configured below.
tool: wasm-pack
- run: yarn build:wasm
yarn-tsc:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- run: yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install
- uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
tool: wasm-pack
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: Build Wasm
shell: bash
run: yarn build:wasm
- uses: actions/upload-artifact@v4
with:
name: prepared-wasm
path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
- uses: actions/upload-artifact@v4
with:
name: prepared-ts-rs-bindings
path: |
rust/kcl-lib/bindings/*
yarn-tsc:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn build:wasm
- run: yarn tsc
yarn-lint:
runs-on: ubuntu-latest
needs: yarn-build-wasm
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
@ -117,55 +73,9 @@ jobs:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install
- run: yarn lint
yarn-circular-dependencies:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn circular-deps:diff
python-codespell:
runs-on: ubuntu-22.04
steps:
@ -181,7 +91,6 @@ jobs:
yarn-unit-test-kcl-samples:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
@ -194,22 +103,7 @@ jobs:
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
tool: wasm-pack
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn build:wasm
- run: yarn simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
@ -226,7 +120,6 @@ jobs:
yarn-unit-test:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
@ -239,22 +132,7 @@ jobs:
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
tool: wasm-pack
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn build:wasm
- run: yarn simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
@ -263,13 +141,13 @@ jobs:
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: yarn playwright install chromium --with-deps
- name: Run unit tests
- name: run unit tests
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: xvfb-run -a yarn test:unit
env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: Check for changes
- name: check for changes
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
id: git-check
run: |

View File

@ -14,32 +14,16 @@ permissions:
jobs:
update-branch:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- name: Sync with main
- shell: bash
run: |
# Create the branch
# checkout our branch
git checkout all-e2e || git checkout -b all-e2e
# Reset to main
# fetch origin
git fetch origin
# reset to main
git reset --hard origin/main
# Get a new SHA to prevent overwriting the commit status on main
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit --allow-empty --message="[all-e2e] $(git log --max-count=1 --pretty=%B)"
# Overwrite the branch
git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}.git
# force push it
git push --force origin all-e2e

View File

@ -1,27 +1,27 @@
# Setting Up Zoo Design Studio
# Setting Up Zoo Modeling App
Compared to other CAD software, getting Zoo Design Studio up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install.
Compared to other CAD software, getting Zoo Modeling App up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install.
## Windows
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
2. Once downloaded, run the installer `Zoo Design Studio-{version}-{arch}-win.exe` which should take a few seconds.
2. Once downloaded, run the installer `Zoo Modeling App-{version}-{arch}-win.exe` which should take a few seconds.
3. The installation happens at `C:\Program Files\Zoo Design Studio`. A shortcut in the start menu is also created so you can run the app easily by clicking on it.
3. The installation happens at `C:\Program Files\Zoo Modeling App`. A shortcut in the start menu is also created so you can run the app easily by clicking on it.
## macOS
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
2. Once downloaded, open the disk image `Zoo Design Studio-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
2. Once downloaded, open the disk image `Zoo Modeling App-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
3. You can then open your `Applications` directory and double-click on `Zoo Design Studio` to open.
3. You can then open your `Applications` directory and double-click on `Zoo Modeling App` to open.
## Linux
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
- On Ubuntu, install the FUSE library with these commands in a terminal.
@ -30,7 +30,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
sudo apt install libfuse2
```
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
- Once installed, copy the downloaded `Zoo Design Studio-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
- Once installed, copy the downloaded `Zoo Modeling App-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
- `appimaged` should automatically find it and make it executable. If not, run:
```bash

View File

@ -4,69 +4,41 @@ all: install build check
###############################################################################
# INSTALL
ifeq ($(OS),Windows_NT)
CARGO ?= ~/.cargo/bin/cargo.exe
WASM_PACK ?= ~/.cargo/bin/wasm-pack.exe
else
CARGO ?= ~/.cargo/bin/cargo
WASM_PACK ?= ~/.cargo/bin/wasm-pack
endif
WASM_PACK ?= ~/.cargo/bin/wasm-pack
.PHONY: install
install: node_modules/.yarn-integrity $(CARGO) $(WASM_PACK) ## Install dependencies
install: node_modules/.yarn-integrity $(WASM_PACK) ## Install dependencies
node_modules/.yarn-integrity: package.json yarn.lock
yarn install
ifeq ($(OS),Windows_NT)
@ type nul > $@
else
@ touch $@
endif
$(CARGO):
ifeq ($(OS),Windows_NT)
yarn install:rust:windows
else
yarn install:rust
endif
$(WASM_PACK):
ifeq ($(OS),Windows_NT)
yarn install:wasm-pack:cargo
else
yarn install:rust
yarn install:wasm-pack:sh
endif
###############################################################################
# BUILD
CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*)
RUST_SOURCES := $(wildcard rust/**/*.rs)
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
RUST_SOURCES := $(wildcard rust/*) $(wildcard rust/**/*)
TYPESCRIPT_SOURCES := $(wildcard src/**/*.tsx) $(wildcard src/**/*.ts)
.PHONY: build
build: build-web build-desktop
.PHONY: build-web
build-web: install public/kcl_wasm_lib_bg.wasm build/index.html
build-web: public/kcl_wasm_lib_bg.wasm build/index.html
.PHONY: build-desktop
build-desktop: install public/kcl_wasm_lib_bg.wasm .vite/build/main.js
build-desktop: public/kcl_wasm_lib_bg.wasm .vite/build/main.js
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES) $(RUST_SOURCES)
ifeq ($(OS),Windows_NT)
yarn build:wasm:dev:windows
else
yarn build:wasm:dev
endif
public/kcl_wasm_lib_bg.wasm: $(RUST_SOURCES)
yarn build:wasm
build/index.html: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
build/index.html: $(TYPESCRIPT_SOURCES)
yarn build:local
.vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
.vite/build/main.js: $(TYPESCRIPT_SOURCES)
yarn tronb:vite:dev
###############################################################################
@ -87,10 +59,8 @@ lint: install ## Lint the code
###############################################################################
# RUN
TARGET ?= desktop
.PHONY: run
run: run-$(TARGET)
run: run-web
.PHONY: run-web
run-web: install build-web ## Start the web app
@ -103,60 +73,34 @@ run-desktop: install build-desktop ## Start the desktop app
###############################################################################
# TEST
E2E_GREP ?=
E2E_WORKERS ?=
E2E_FAILURES ?= 1
GREP ?= ""
.PHONY: test
test: test-unit test-e2e
.PHONY: test-unit
test-unit: install ## Run the unit tests
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
@ nc -z localhost 3000 || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
yarn test:unit
.PHONY: test-e2e
test-e2e: test-e2e-$(TARGET)
.PHONY: test-e2e-web
test-e2e-web: install build-web ## Run the web e2e tests
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
ifdef E2E_GREP
yarn chrome:test --headed --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
else
yarn chrome:test --headed --workers='100%'
endif
.PHONY: test-e2e-desktop
test-e2e-desktop: install build-desktop ## Run the desktop e2e tests
ifdef E2E_GREP
yarn test:playwright:electron --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
else
yarn test:playwright:electron --workers='100%'
endif
test-e2e: install build-desktop ## Run the e2e tests
yarn test:playwright:electron --workers=1 --grep=$(GREP)
###############################################################################
# CLEAN
.PHONY: clean
clean: ## Delete all artifacts
ifeq ($(OS),Windows_NT)
git clean --force -d -X
else
rm -rf .vite/ build/
rm -rf trace.zip playwright-report/ test-results/
rm -rf public/kcl_wasm_lib_bg.wasm
rm -rf rust/*/bindings/ rust/*/pkg/ rust/target/
rm -rf node_modules/ rust/*/node_modules/
endif
.PHONY: help
help: install
ifeq ($(OS),Windows_NT)
@ powershell -Command "Get-Content $(MAKEFILE_LIST) | Select-String -Pattern '^[^\s]+:.*##\s.*$$' | ForEach-Object { $$line = $$_.Line -split ':.*?##\s+'; Write-Host -NoNewline $$line[0].PadRight(30) -ForegroundColor Cyan; Write-Host $$line[1] }"
else
@ grep -E '^[^[:space:]]+:.*## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
endif
.DEFAULT_GOAL := help

View File

@ -1,17 +1,17 @@
![Zoo Design Studio](/public/zma-logomark-outlined.png)
![Zoo Modeling App](/public/zma-logomark-outlined.png)
## Zoo Design Studio
## Zoo Modeling App
download at [zoo.dev/modeling-app/download](https://zoo.dev/modeling-app/download)
A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
Design Studio is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence:
Modeling App is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence:
- All artifacts—including parts and assemblies—should be represented as human-readable code. At the end of the day, your CAD project should be "plain text"
- This makes version control—which is a solved problem in software engineering—trivial for CAD
- All GUI (or point-and-click) interactions should be actions performed on this code representation under the hood
- This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in Design Studio
- This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in Modeling App
- Everything graphics _has_ to be built for the GPU
- Most CAD applications have had to retrofit support for GPUs, but our geometry engine is made for GPUs (primarily Nvidia's Vulkan), getting the order of magnitude rendering performance boost with it
- Make the resource-intensive pieces of an application auto-scaling
@ -19,9 +19,9 @@ Design Studio is our take on what a modern modelling experience can be. It is ap
We are excited about what a small team of people could build in a short time with our API. We welcome you to try our API, build your own applications, or contribute to ours!
Design Studio is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more.
Modeling App is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more.
The 3D view in Design Studio is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine.
The 3D view in Modeling App is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine.
## Tools
@ -198,13 +198,13 @@ If the prompt doesn't show up, start the app in command line to grab the electro
```
# Windows (PowerShell)
& 'C:\Program Files\Zoo Design Studio\Zoo Design Studio.exe'
& 'C:\Program Files\Zoo Modeling App\Zoo Modeling App.exe'
# macOS
/Applications/Zoo\ Modeling\ App.app/Contents/MacOS/Zoo\ Modeling\ App
# Linux
./Zoo Design Studio-{version}-{arch}-linux.AppImage
./Zoo Modeling App-{version}-{arch}-linux.AppImage
```
#### 4. Publish the release

View File

@ -54,7 +54,7 @@ example = extrude(exampleSketch, length = 5)
// Add color to a revolved solid.
sketch001 = startSketchOn(XY)
|> circle(center = [15, 0], radius = 5)
|> revolve(angle = 360, axis = Y)
|> revolve(angle = 360, axis = 'y')
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)
```

60
docs/kcl/circle.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -10,10 +10,10 @@ Construct a circle derived from 3 points.
```js
circleThreePoint(
sketchSurfaceOrGroup: SketchOrSurface,
p1: [number],
p2: [number],
p3: [number],
sketchSurfaceOrGroup: SketchOrSurface,
tag?: TagDeclarator,
): Sketch
```
@ -23,10 +23,10 @@ circleThreePoint(
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
| `p1` | [`[number]`](/docs/kcl/types/number) | 1st point to derive the circle. | Yes |
| `p2` | [`[number]`](/docs/kcl/types/number) | 2nd point to derive the circle. | Yes |
| `p3` | [`[number]`](/docs/kcl/types/number) | 3rd point to derive the circle. | Yes |
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Identifier for the circle to reference elsewhere. | No |
### Returns

View File

@ -9,12 +9,13 @@ layout: manual
### `std`
- [`X`](/docs/kcl/consts/std-X)
- [`HALF_TURN`](/docs/kcl/consts/std-HALF_TURN)
- [`QUARTER_TURN`](/docs/kcl/consts/std-QUARTER_TURN)
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-THREE_QUARTER_TURN)
- [`XY`](/docs/kcl/consts/std-XY)
- [`XZ`](/docs/kcl/consts/std-XZ)
- [`Y`](/docs/kcl/consts/std-Y)
- [`YZ`](/docs/kcl/consts/std-YZ)
- [`Z`](/docs/kcl/consts/std-Z)
- [`ZERO`](/docs/kcl/consts/std-ZERO)
### `std::math`
@ -22,10 +23,3 @@ layout: manual
- [`PI`](/docs/kcl/consts/std-math-PI)
- [`TAU`](/docs/kcl/consts/std-math-TAU)
### `std::turns`
- [`HALF_TURN`](/docs/kcl/consts/std-turns-HALF_TURN)
- [`QUARTER_TURN`](/docs/kcl/consts/std-turns-QUARTER_TURN)
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-turns-THREE_QUARTER_TURN)
- [`ZERO`](/docs/kcl/consts/std-turns-ZERO)

View File

@ -0,0 +1,15 @@
---
title: "std::HALF_TURN"
excerpt: ""
layout: manual
---
```js
std::HALF_TURN: number(deg) = 180deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::QUARTER_TURN: number(deg) = 90deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::THREE_QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::THREE_QUARTER_TURN: number(deg) = 270deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::Y"
excerpt: ""
layout: manual
---
```js
std::Y
```

View File

@ -1,15 +0,0 @@
---
title: "std::Z"
excerpt: ""
layout: manual
---
```js
std::Z
```

View File

@ -1,5 +1,5 @@
---
title: "std::X"
title: "std::ZERO"
excerpt: ""
layout: manual
---
@ -9,7 +9,7 @@ layout: manual
```js
std::X
std::ZERO: number = 0
```

View File

@ -15,7 +15,7 @@ std::math::E: number = 2.71828182845904523536028747135266250_
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 30,

View File

@ -17,7 +17,7 @@ std::math::PI: number = 3.14159265358979323846264338327950288_
```js
circumference = 70
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> circle(center = [0, 0], radius = circumference/ (2 * PI))
example = extrude(exampleSketch, length = 5)

View File

@ -15,7 +15,7 @@ std::math::TAU: number = 6.28318530717958647692528676655900577_
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 50,

View File

@ -1,15 +0,0 @@
---
title: "std::turns::HALF_TURN"
excerpt: ""
layout: manual
---
```js
std::turns::HALF_TURN: number(deg) = 180deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::turns::QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::turns::QUARTER_TURN: number(deg) = 90deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::turns::THREE_QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::turns::THREE_QUARTER_TURN: number(deg) = 270deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::turns::ZERO"
excerpt: ""
layout: manual
---
```js
std::turns::ZERO: number = 0
```

107
docs/kcl/helix.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@ Import a CAD file.
For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.
Note: The import command currently only works when using the native Design Studio.
Note: The import command currently only works when using the native Modeling App.
```js
import(

View File

@ -1,6 +1,6 @@
---
title: "KCL Standard Library"
excerpt: "Documentation for the KCL standard library for the Zoo Design Studio."
excerpt: "Documentation for the KCL standard library for the Zoo Modeling App."
layout: manual
---
@ -22,22 +22,20 @@ layout: manual
* [`string`](kcl/types/string)
* [`tag`](kcl/types/tag)
* **std**
* [`Axis2d`](kcl/types/Axis2d)
* [`Axis3d`](kcl/types/Axis3d)
* [`Edge`](kcl/types/Edge)
* [`Face`](kcl/types/Face)
* [`HALF_TURN`](kcl/consts/std-HALF_TURN)
* [`Helix`](kcl/types/Helix)
* [`Plane`](kcl/types/Plane)
* [`Point2d`](kcl/types/Point2d)
* [`Point3d`](kcl/types/Point3d)
* [`QUARTER_TURN`](kcl/consts/std-QUARTER_TURN)
* [`Sketch`](kcl/types/Sketch)
* [`Solid`](kcl/types/Solid)
* [`X`](kcl/consts/std-X)
* [`THREE_QUARTER_TURN`](kcl/consts/std-THREE_QUARTER_TURN)
* [`XY`](kcl/consts/std-XY)
* [`XZ`](kcl/consts/std-XZ)
* [`Y`](kcl/consts/std-Y)
* [`YZ`](kcl/consts/std-YZ)
* [`Z`](kcl/consts/std-Z)
* [`ZERO`](kcl/consts/std-ZERO)
* [`abs`](kcl/abs)
* [`acos`](kcl/acos)
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
@ -63,6 +61,7 @@ layout: manual
* [`bezierCurve`](kcl/bezierCurve)
* [`ceil`](kcl/ceil)
* [`chamfer`](kcl/chamfer)
* [`circle`](kcl/circle)
* [`circleThreePoint`](kcl/circleThreePoint)
* [`close`](kcl/close)
* [`cm`](kcl/cm)
@ -74,7 +73,8 @@ layout: manual
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
* [`getOppositeEdge`](kcl/getOppositeEdge)
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
* [`helix`](kcl/std-helix)
* [`helix`](kcl/helix)
* [`helixRevolutions`](kcl/helixRevolutions)
* [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`inch`](kcl/inch)
@ -93,6 +93,7 @@ layout: manual
* [`map`](kcl/map)
* [`max`](kcl/max)
* [`min`](kcl/min)
* [`mirror2d`](kcl/mirror2d)
* [`mm`](kcl/mm)
* [`offsetPlane`](kcl/offsetPlane)
* [`patternCircular2d`](kcl/patternCircular2d)
@ -111,7 +112,7 @@ layout: manual
* [`push`](kcl/push)
* [`reduce`](kcl/reduce)
* [`rem`](kcl/rem)
* [`revolve`](kcl/std-revolve)
* [`revolve`](kcl/revolve)
* [`rotate`](kcl/rotate)
* [`round`](kcl/round)
* [`scale`](kcl/scale)
@ -145,11 +146,3 @@ layout: manual
* [`cos`](kcl/std-math-cos)
* [`sin`](kcl/std-math-sin)
* [`tan`](kcl/std-math-tan)
* **std::sketch**
* [`circle`](kcl/std-sketch-circle)
* [`mirror2d`](kcl/std-sketch-mirror2d)
* **std::turns**
* [`turns::HALF_TURN`](kcl/consts/std-turns-HALF_TURN)
* [`turns::QUARTER_TURN`](kcl/consts/std-turns-QUARTER_TURN)
* [`turns::THREE_QUARTER_TURN`](kcl/consts/std-turns-THREE_QUARTER_TURN)
* [`turns::ZERO`](kcl/consts/std-turns-ZERO)

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
---
title: "KCL Known Issues"
excerpt: "Known issues with the KCL standard library for the Zoo Design Studio."
excerpt: "Known issues with the KCL standard library for the Zoo Modeling App."
layout: manual
---

104
docs/kcl/mirror2d.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
---
title: "KCL Modules"
excerpt: "Documentation of modules for the KCL language for the Zoo Design Studio."
excerpt: "Documentation of modules for the KCL language for the Zoo Modeling App."
layout: manual
---
@ -95,7 +95,7 @@ import "tests/inputs/cube.obj"
When importing a GLTF file, the bin file will be imported as well.
Import paths are relative to the current project directory. Imports currently only work when
using the native Design Studio, not in the browser.
using the native Modeling App, not in the browser.
### Supported values

237
docs/kcl/revolve.md Normal file

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

@ -1,12 +1,12 @@
---
title: "KCL Settings"
excerpt: "Documentation of settings for the KCL language and Zoo Design Studio."
excerpt: "Documentation of settings for the KCL language and Zoo Modeling App."
layout: manual
---
# KCL Settings
There are three levels of settings available in the KittyCAD Design Studiolication:
There are three levels of settings available in the KittyCAD modeling application:
1. [User Settings](/docs/kcl/settings/user): Global settings that apply to all projects, stored in `user.toml`
2. [Project Settings](/docs/kcl/settings/project): Settings specific to a project, stored in `project.toml`
@ -14,7 +14,7 @@ There are three levels of settings available in the KittyCAD Design Studiolicati
## Configuration Files
The KittyCAD Design Studio uses TOML files for configuration:
The KittyCAD modeling app uses TOML files for configuration:
* **User Settings**: `user.toml` - See [complete documentation](/docs/kcl/settings/user)
* **Project Settings**: `project.toml` - See [complete documentation](/docs/kcl/settings/project)

View File

@ -35,7 +35,7 @@ base_unit = "in"
#### app
The settings for the Design Studio.
The settings for the modeling app.
**Default:** None

View File

@ -37,7 +37,7 @@ text_wrapping = false
#### app
The settings for the Design Studio.
The settings for the modeling app.
**Default:** None

View File

@ -146,7 +146,7 @@ exampleSketch = startSketchOn(XY)
|> line(end = [-2, 0])
|> close()
example = revolve(exampleSketch, axis = Y, angle = 180)
example = revolve(exampleSketch, axis = 'y', angle = 180)
exampleSketch002 = startSketchOn(example, 'end')
|> startProfileAt([4.5, -5], %)
@ -177,7 +177,7 @@ exampleSketch = startSketchOn(XY)
example = revolve(
exampleSketch,
axis = Y,
axis = 'y',
angle = 180,
tagEnd = $end01,
)

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ Compute the cosine of a number (in radians).
```js
cos(@num: number(rad)): number(_)
cos(num: number(rad)): number(_)
```
@ -27,7 +27,7 @@ cos(@num: number(rad)): number(_)
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 30,

View File

@ -9,7 +9,7 @@ Compute the sine of a number (in radians).
```js
sin(@num: number(rad)): number(_)
sin(num: number(rad)): number(_)
```
@ -27,7 +27,7 @@ sin(@num: number(rad)): number(_)
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 50,

View File

@ -9,7 +9,7 @@ Compute the tangent of a number (in radians).
```js
tan(@num: number(rad)): number(_)
tan(num: number(rad)): number(_)
```
@ -27,7 +27,7 @@ tan(@num: number(rad)): number(_)
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 50,

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

File diff suppressed because one or more lines are too long

View File

@ -74,7 +74,7 @@ helixPath = helix(
revolutions = 4,
length = 10,
radius = 5,
axis = Z,
axis = 'Z',
)
// Create a spring by sweeping around the helix path.

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
---
title: "KCL Types"
excerpt: "Documentation of types for the KCL standard library for the Zoo Design Studio."
excerpt: "Documentation of types for the KCL standard library for the Zoo Modeling App."
layout: manual
---

View File

@ -1,12 +0,0 @@
---
title: "std::Axis2d"
excerpt: "An infinite line in 2d space."
layout: manual
---
An infinite line in 2d space.

View File

@ -1,12 +0,0 @@
---
title: "std::Axis3d"
excerpt: "An infinite line in 3d space."
layout: manual
---
An infinite line in 3d space.

View File

@ -1,12 +0,0 @@
---
title: "std::Edge"
excerpt: "The edge of a solid."
layout: manual
---
The edge of a solid.

View File

@ -28,7 +28,7 @@ An extrude plane.
| `faceId` |[`string`](/docs/kcl/types/string)| The face id for the extrude plane. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
----
@ -48,7 +48,7 @@ An extruded arc.
| `faceId` |[`string`](/docs/kcl/types/string)| The face id for the extrude plane. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
----
@ -68,7 +68,7 @@ Geometry metadata.
| `faceId` |[`string`](/docs/kcl/types/string)| The id for the chamfer surface. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
----
@ -88,7 +88,7 @@ Geometry metadata.
| `faceId` |[`string`](/docs/kcl/types/string)| The id for the fillet surface. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
----

View File

@ -17,6 +17,6 @@ Geometry metadata.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |

View File

@ -17,11 +17,10 @@ A helix.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `value` |[`string`](/docs/kcl/types/string)| The id of the helix. | No |
| `artifactId` |[`string`](/docs/kcl/types/string)| The artifact ID. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `revolutions` |[`number`](/docs/kcl/types/number)| Number of revolutions. | No |
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `cylinderId` |[`string`](/docs/kcl/types/string)| The cylinder the helix was created on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |

View File

@ -285,7 +285,7 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Module`| | No |
| `value` |`integer`| Identifier of a source file. Uses a u32 to keep the size small. | No |
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No |
----

View File

@ -10,8 +10,8 @@ A point in two dimensional space.
type Point2d = [number; 2]
```
[`Point2d`](/docs/kcl/types/Point2d) is an alias for a two-element array of [number](/docs/kcl/types/number)s. To write a value
with type [`Point2d`](/docs/kcl/types/Point2d), use an array, e.g., `[0, 0]` or `[5.0, 3.14]`.
`Point2d` is an alias for a two-element array of [number](/docs/kcl/types/number)s. To write a value
with type `Point2d`, use an array, e.g., `[0, 0]` or `[5.0, 3.14]`.

View File

@ -10,8 +10,8 @@ A point in three dimensional space.
type Point3d = [number; 3]
```
[`Point3d`](/docs/kcl/types/Point3d) is an alias for a three-element array of [number](/docs/kcl/types/number)s. To write a value
with type [`Point3d`](/docs/kcl/types/Point3d), use an array, e.g., `[0, 0, 0]` or `[5.0, 3.14, 6.8]`.
`Point3d` is an alias for a three-element array of [number](/docs/kcl/types/number)s. To write a value
with type `Point3d`, use an array, e.g., `[0, 0, 0]` or `[5.0, 3.14, 6.8]`.

View File

@ -17,6 +17,6 @@ Data for polar coordinates.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `angle` |[`number`](/docs/kcl/types/number)| The angle of the line (in degrees). | No |
| `length` |[`number`](/docs/kcl/types/number)| The length of the line. | No |
| `length` |[`TyF64`](/docs/kcl/types/TyF64)| The length of the line. | No |

View File

@ -17,7 +17,7 @@ mySketch = startSketchOn('XY')
|> close()
```
The `mySketch` variable will be an executed [`Sketch`](/docs/kcl/types/Sketch) object. Executed being past
The `mySketch` variable will be an executed `Sketch` object. Executed being past
tense, because the engine has already executed the commands to create the sketch.
The previous sketch commands will never be executed again, in this case.

View File

@ -25,7 +25,7 @@ A sketch type.
|----------|------|-------------|----------|
| `type` |enum: `plane`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the plane. | No |
| `artifactId` |[`string`](/docs/kcl/types/string)| The artifact ID. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Type for a plane. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
@ -49,7 +49,7 @@ A face.
|----------|------|-------------|----------|
| `type` |enum: `face`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the face. | No |
| `artifactId` |[`string`](/docs/kcl/types/string)| The artifact ID. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `value` |[`string`](/docs/kcl/types/string)| The tag of the face. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |

View File

@ -18,7 +18,7 @@ myPart = startSketchOn('XY')
|> extrude(length = 6)
```
The `myPart` variable will be an executed [`Solid`](/docs/kcl/types/Solid) object. Executed being past
The `myPart` variable will be an executed `Solid` object. Executed being past
tense, because the engine has already executed the commands to create the solid.
The previous solid commands will never be executed again, in this case.

View File

@ -0,0 +1,15 @@
---
title: "SourceRange"
excerpt: ""
layout: manual
---
**Type:** `integer` (`uint`)

View File

@ -5,11 +5,17 @@ layout: manual
---
**Type:** [`number`](/docs/kcl/types/number) (`double`)
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `n` |[`number`](/docs/kcl/types/number)| | No |
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No |

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
test.describe('Electron app header tests', () => {
test(

View File

@ -1,14 +1,13 @@
import type { Page } from '@playwright/test'
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import {
PERSIST_MODELING_CONTEXT,
getUtils,
TEST_COLORS,
commonPoints,
getUtils,
PERSIST_MODELING_CONTEXT,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import { HomePageFixture } from './fixtures/homePageFixture'
test.setTimeout(120000)
@ -86,7 +85,7 @@ async function doBasicSketch(
await page.mouse.click(startXPx, 500 - PUR * 20)
if (openPanes.includes('code')) {
await expect(u.codeLocator)
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1})
@ -120,7 +119,10 @@ async function doBasicSketch(
await page.waitForTimeout(100)
if (openPanes.includes('code')) {
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
await expect(
await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)
).toBeLessThan(3)
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
}
// hold down shift
@ -143,7 +145,7 @@ async function doBasicSketch(
// Open the code pane.
await u.openKclCodePanel()
await expect(u.codeLocator)
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1}, tag = $seg01)

View File

@ -1,118 +0,0 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Point and click for boolean workflows', () => {
// Boolean operations to test
const booleanOperations = [
{
name: 'union',
code: 'union([extrude001, extrude006])',
},
{
name: 'subtract',
code: 'subtract([extrude001], tools = [extrude006])',
},
{
name: 'intersect',
code: 'intersect([extrude001, extrude006])',
},
] as const
for (let i = 0; i < booleanOperations.length; i++) {
const operation = booleanOperations[i]
const operationName = operation.name
const commandName = `Boolean ${
operationName.charAt(0).toUpperCase() + operationName.slice(1)
}`
test(`Create boolean operation -- ${operationName}`, async ({
context,
homePage,
cmdBar,
editor,
toolbar,
scene,
page,
}) => {
const file = await fs.readFile(
path.resolve(
__dirname,
'../../',
'./rust/kcl-lib/e2e/executor/inputs/boolean-setup-with'
),
'utf-8'
)
await context.addInitScript((file) => {
localStorage.setItem('persistCode', file)
}, file)
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
await scene.settled(cmdBar)
// Test coordinates for selection - these might need adjustment based on actual scene layout
const cylinderPoint = { x: 592, y: 174 }
const secondObjectPoint = { x: 683, y: 273 }
// Create mouse helpers for selecting objects
const [clickFirstObject] = scene.makeMouseHelpers(
cylinderPoint.x,
cylinderPoint.y,
{ steps: 10 }
)
const [clickSecondObject] = scene.makeMouseHelpers(
secondObjectPoint.x,
secondObjectPoint.y,
{ steps: 10 }
)
await test.step(`Test ${operationName} operation`, async () => {
// Click the boolean operation button in the toolbar
await toolbar.selectBoolean(operationName)
// Verify command bar is showing the right command
await expect(cmdBar.page.getByTestId('command-name')).toContainText(
commandName
)
// Select first object in the scene, expect there to be a pixel diff from the selection color change
await clickFirstObject({ pixelDiff: 50 })
// For subtract, we need to proceed to the next step before selecting the second object
if (operationName !== 'subtract') {
// should down shift key to select multiple objects
await page.keyboard.down('Shift')
}
// Select second object
await clickSecondObject({ pixelDiff: 50 })
// Confirm the operation in the command bar
await cmdBar.progressCmdBar()
if (operationName === 'union' || operationName === 'intersect') {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Solids: '2 paths',
},
commandName,
})
} else if (operationName === 'subtract') {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Tool: '1 path',
Target: '1 path',
},
commandName,
})
}
await cmdBar.submit()
await editor.expectEditor.toContain(operation.code)
})
})
}
})

View File

@ -1,11 +1,10 @@
import type { Page } from '@playwright/test'
import type { EngineCommand } from '@src/lang/std/artifactGraph'
import { uuidv4 } from '@src/lib/utils'
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { HomePageFixture } from './fixtures/homePageFixture'
import { getUtils } from './test-utils'
import { EngineCommand } from 'lang/std/artifactGraph'
import { uuidv4 } from 'lib/utils'
import { SceneFixture } from './fixtures/sceneFixture'
test.describe(
'Can create sketches on all planes and their back sides',
@ -47,7 +46,7 @@ test.describe(
},
}
const code = `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)`
const code = `@settings(defaultLengthUnit = in)sketch001 = startSketchOn('${plane}')profile001 = startProfileAt([0.91, -1.22], sketch001)`
await u.openDebugPanel()

View File

@ -1,14 +1,13 @@
import { bracket } from '@src/lib/exampleKcl'
import fsp from 'fs/promises'
import { join } from 'path'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from '@e2e/playwright/storageStates'
import { test, expect } from './zoo-test'
import {
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
getUtils,
executorInputPath,
} from './test-utils'
import { join } from 'path'
import { bracket } from 'lib/exampleKcl'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
import fsp from 'fs/promises'
test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
test('Typing KCL errors induces a badge on the code pane button', async ({
@ -22,15 +21,14 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
await page.addInitScript(() => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
// Extruded Triangle
sketch001 = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(end = [10, 0])
|> line(end = [-5, 10])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch001, length = 5)`
`// Extruded Triangle
sketch001 = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(end = [10, 0])
|> line(end = [-5, 10])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch001, length = 5)`
)
})

View File

@ -1,13 +1,12 @@
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
import { test, expect } from './zoo-test'
import * as fsp from 'fs/promises'
import path, { join } from 'path'
import {
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
import path, { join } from 'path'
test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
test('Extrude from command bar selects extrude line after', async ({
@ -514,8 +513,7 @@ c = 3 + a`
await homePage.openProject(projectName)
// TODO: you probably shouldn't need an engine connection to add a parameter,
// but you do because all modeling commands have that requirement
// Don't use scene.settled here
await expect(scene.startEditSketchBtn).toBeEnabled({ timeout: 15_000 })
await scene.settled(cmdBar)
await test.step(`Create a parameter via command bar`, async () => {
await cmdBar.cmdBarOpenBtn.click()
@ -544,12 +542,7 @@ c = 3 + a`
)
const newValue = `2 * b + a`
await test.step(`Edit the parameter via command bar`, async () => {
// TODO: make the command palette command registration more static, and the enabled state more dynamic
// so that we can just open the command palette and know all commands will be there.
await expect(scene.startEditSketchBtn).toBeEnabled()
await cmdBar.cmdBarOpenBtn.click()
await cmdBar.chooseCommand('edit parameter')
await cmdBar.expectState({

View File

@ -1,5 +1,5 @@
import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
import { getUtils } from './test-utils'
test.describe('Copilot ghost text', () => {
// eslint-disable-next-line jest/valid-title

View File

@ -1,5 +1,6 @@
import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
import { getUtils } from './test-utils'
function countNewlines(input: string): number {
let count = 0

View File

@ -1,12 +1,11 @@
import fsp from 'fs/promises'
import { test, expect } from './zoo-test'
import path from 'path'
import {
getUtils,
executorInputPath,
getPlaywrightDownloadDir,
getUtils,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import fsp from 'fs/promises'
test(
'export works on the first try',
@ -32,44 +31,57 @@ test(
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await test.step('on open of project', async () => {
// Open the project
const projectName = page.getByText(`bracket`)
await expect(projectName).toBeVisible()
await projectName.click()
await scene.waitForExecutionDone()
await page.waitForTimeout(1_000) // wait for panel buttons to be available
page.on('console', console.log)
// Expect zero errors in gutter
await test.step('on open of project', async () => {
await expect(page.getByText(`bracket`)).toBeVisible()
// open the project
await page.getByText(`bracket`).click()
// expect zero errors in guter
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// Click the export button
// export the model
const exportButton = page.getByTestId('export-pane-button')
await expect(exportButton).toBeVisible()
await exportButton.click()
await page.waitForTimeout(1_000) // wait for export options to be available
// Select the first format option
// Wait for the model to finish loading
const modelStateIndicator = page.getByTestId(
'model-state-indicator-execution-done'
)
await expect(modelStateIndicator).toBeVisible({ timeout: 60000 })
const gltfOption = page.getByText('glTF')
const exportFileName = `main.gltf` // source file is named `main.kcl`
const submitButton = page.getByText('Confirm Export')
const exportingToastMessage = page.getByText(`Exporting...`)
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()
await expect(gltfOption).toBeVisible()
await expect(page.getByText('STL')).toBeVisible()
await page.keyboard.press('Enter')
// Click the checkbox
const submitButton = page.getByText('Confirm Export')
await expect(submitButton).toBeVisible()
await page.waitForTimeout(500)
await page.keyboard.press('Enter')
// Find the toast.
// Look out for the toast message
const exportingToastMessage = page.getByText(`Exporting...`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
await expect(exportingToastMessage).toBeVisible()
await expect(alreadyExportingToastMessage).not.toBeVisible()
// Expect it to succeed
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
// Expect it to succeed.
await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible()
@ -77,7 +89,6 @@ test(
await expect(successToastMessage).toBeVisible()
await expect(exportingToastMessage).not.toBeVisible()
// Check for the exported file
const firstFileFullPath = path.resolve(
getPlaywrightDownloadDir(tronApp.projectDirName),
exportFileName
@ -104,53 +115,60 @@ test(
const u = await getUtils(page)
await u.openFilePanel()
// Click on the other file
const otherKclButton = page.getByRole('button', { name: 'other.kcl' })
// Click the file
await otherKclButton.click()
// Close the file pane
await u.closeFilePanel()
await scene.waitForExecutionDone()
await page.waitForTimeout(1_000) // wait for panel buttons to be available
// Expect zero errors in gutter
// FIXME: await scene.waitForExecutionDone() does not work. The modeling indicator stays in -receive-reliable and not execution done
await page.waitForTimeout(10000)
// expect zero errors in guter
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// Click the export button
// export the model
const exportButton = page.getByTestId('export-pane-button')
await expect(exportButton).toBeVisible()
await exportButton.click()
await page.waitForTimeout(1_000) // wait for export options to be available
// Select the first format option
const gltfOption = page.getByText('glTF')
const exportFileName = `other.gltf` // source file is named `other.kcl`
const submitButton = page.getByText('Confirm Export')
const exportingToastMessage = page.getByText(`Exporting...`)
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()
await expect(gltfOption).toBeVisible()
await expect(page.getByText('STL')).toBeVisible()
await page.keyboard.press('Enter')
// Click the checkbox
const submitButton = page.getByText('Confirm Export')
await expect(submitButton).toBeVisible()
await page.waitForTimeout(500)
await page.keyboard.press('Enter')
// Find the toast.
// Look out for the toast message
const exportingToastMessage = page.getByText(`Exporting...`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
await expect(exportingToastMessage).toBeVisible()
await expect(alreadyExportingToastMessage).not.toBeVisible()
// Expect it to succeed
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`)
await expect(successToastMessage).toBeVisible()
await expect(exportingToastMessage).not.toBeVisible()
await test.step('Check the success toast message shows and nothing else', async () =>
Promise.all([
expect(alreadyExportingToastMessage).not.toBeVisible(),
expect(errorToastMessage).not.toBeVisible(),
expect(engineErrorToastMessage).not.toBeVisible(),
expect(successToastMessage).toBeVisible(),
expect(exportingToastMessage).not.toBeVisible(),
]))
// Check for the exported file=
const secondFileFullPath = path.resolve(
getPlaywrightDownloadDir(tronApp.projectDirName),
exportFileName

View File

@ -1,14 +1,14 @@
import { uuidv4 } from '@src/lib/utils'
import { test, expect } from './zoo-test'
import fsp from 'fs/promises'
import { join } from 'path'
import { uuidv4 } from 'lib/utils'
import {
TEST_COLORS,
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
TEST_COLORS,
} from './test-utils'
import { join } from 'path'
test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
test('can comment out code with ctrl+/', async ({ page, homePage }) => {
@ -34,7 +34,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
@ -49,7 +49,7 @@ sketch001 = startSketchOn(XY)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
@ -184,7 +184,7 @@ sketch001 = startSketchOn(XY)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
@ -235,7 +235,7 @@ sketch001 = startSketchOn(XY)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch_001 = startSketchOn(XY)
sketch_001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
@ -479,7 +479,6 @@ sketch_001 = startSketchOn(XY)
test('if you write kcl with lint errors you get lints', async ({
page,
homePage,
scene,
}) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1000, height: 500 })
@ -499,7 +498,10 @@ sketch_001 = startSketchOn(XY)
await page.keyboard.press('ArrowLeft')
await page.keyboard.press('ArrowRight')
await scene.waitForExecutionDone()
// FIXME: lsp errors do not propagate to the frontend until engine is connected and code is executed
// This timeout is to wait for engine connection. LSP and code execution errors should be handled differently
// LSP can emit errors as fast as it waits and show them in the editor
await page.waitForTimeout(10000)
// error in guter
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
@ -823,7 +825,7 @@ sketch_001 = startSketchOn(XY)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn('XZ')
|> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
@ -898,7 +900,7 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn('XZ')
|> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
@ -985,13 +987,12 @@ sketch001 = startSketchOn(XZ)
test(
'Can undo a sketch modification with ctrl+z',
{ tag: ['@skipWin'] },
async ({ page, homePage, editor }) => {
async ({ page, homePage }) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit=in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
@ -1081,45 +1082,41 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %)
|> line(end = [15.4, -2.78])
|> tangentialArcTo([27.6, -3.05], %)
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
)
|> extrude(length = 5)
`)
// Hit undo
await page.keyboard.down('Control')
await page.keyboard.press('KeyZ')
await page.keyboard.up('Control')
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %)
|> line(end = [15.4, -2.78])
|> tangentialArcTo([24.95, -0.38], %)
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
)
|> extrude(length = 5)`)
// Hit undo again.
await page.keyboard.down('Control')
await page.keyboard.press('KeyZ')
await page.keyboard.up('Control')
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
)
|> extrude(length = 5)
`)
// Hit undo again.
await page.keyboard.down('Control')
@ -1127,15 +1124,13 @@ sketch001 = startSketchOn(XZ)
await page.keyboard.up('Control')
await page.waitForTimeout(100)
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> close()
|> extrude(length = 5)`)
}
)
@ -1223,130 +1218,4 @@ sketch001 = startSketchOn(XZ)
})
}
)
test('Rectangle tool panning with middle click', async ({
page,
homePage,
toolbar,
scene,
cmdBar,
editor,
}) => {
await page.setBodyDimensions({ width: 1200, height: 900 })
await homePage.goToModelingScene()
// wait until scene is ready to be interacted with
await scene.connectionEstablished()
await scene.settled(cmdBar)
await page.getByRole('button', { name: 'Start Sketch' }).click()
// select an axis plane
await page.mouse.click(700, 200)
// Needed as we don't yet have a way to get a signal from the engine that the camera has animated to the sketch plane
await page.waitForTimeout(1000)
const middleMousePan = async (
startX: number,
startY: number,
endX: number,
endY: number
) => {
const initialCode = await editor.getCurrentCode()
await page.mouse.click(startX, startY, { button: 'middle' })
await page.mouse.move(endX, endY, {
steps: 10,
})
// We expect the code to be the same, middle mouse click should not modify the code, only do panning
await editor.expectEditor.toBe(initialCode)
}
await test.step(`Verify corner rectangle panning`, async () => {
await page.getByTestId('corner-rectangle').click()
await middleMousePan(800, 500, 900, 600)
})
await test.step(`Verify center rectangle panning`, async () => {
await toolbar.selectCenterRectangle()
await middleMousePan(800, 200, 900, 300)
})
})
test('Can select lines on the main axis', async ({
page,
homePage,
toolbar,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([100.00, 100.0], sketch001)
|> yLine(length = -100.0)
|> xLine(length = 200.0)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`
)
})
const width = 1200
const height = 800
const viewportSize = { width, height }
await page.setBodyDimensions(viewportSize)
await homePage.goToModelingScene()
const u = await getUtils(page)
await u.waitForPageLoad()
await toolbar.editSketch(0)
await page.waitForTimeout(1000)
// Click on the bottom segment that lies on the x axis
await page.mouse.click(width * 0.85, height / 2)
await page.waitForTimeout(1000)
// Verify segment is selected (you can check for visual indicators or state)
const element = page.locator('[data-overlay-index="1"]')
await expect(element).toHaveAttribute('data-overlay-visible', 'true')
})
test(`Only show axis planes when there are no errors`, async ({
page,
homePage,
scene,
cmdBar,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`sketch001 = startSketchOn(XZ)
profile001 = circle(sketch001, center = [-100.0, -100.0], radius = 50.0)
sketch002 = startSketchOn(XZ)
profile002 = circle(sketch002, center = [-100.0, 100.0], radius = 50.0)
extrude001 = extrude(profile002, length = 0)` // length = 0 is causing the error
)
})
const viewportSize = { width: 1200, height: 800 }
await page.setBodyDimensions(viewportSize)
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
await scene.expectPixelColor(
TEST_COLORS.DARK_MODE_BKGD,
// This is a position where the blue part of the axis plane is visible if its rendered
{ x: viewportSize.width * 0.75, y: viewportSize.height * 0.2 },
15
)
})
})

View File

@ -1,8 +1,7 @@
import { test, expect } from './zoo-test'
import * as fsp from 'fs/promises'
import { join } from 'path'
import { expect, test } from '@e2e/playwright/zoo-test'
const FEATURE_TREE_EXAMPLE_CODE = `export fn timesFive(x) {
return 5 * x
}
@ -22,7 +21,7 @@ sketch001 = startSketchOn(XZ)
|> angledLine([-45, length001], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
revolve001 = revolve(sketch001, axis = X)
revolve001 = revolve(sketch001, axis = "X")
triangle()
|> extrude(length = 30)
plane001 = offsetPlane(XY, offset = 10)
@ -127,7 +126,7 @@ test.describe('Feature Tree pane', () => {
await testViewSource({
operationName: 'Revolve',
operationIndex: 0,
expectedActiveLine: 'revolve001 = revolve(sketch001, axis = X)',
expectedActiveLine: 'revolve001 = revolve(sketch001, axis = "X")',
})
await testViewSource({
operationName: 'Triangle',

View File

@ -1,25 +1,20 @@
import { FILE_EXT } from '@src/lib/constants'
import * as fs from 'fs'
import { test, expect } from './zoo-test'
import * as fsp from 'fs/promises'
import { join } from 'path'
import * as fs from 'fs'
import {
createProject,
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
runningOnWindows,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import { join } from 'path'
import { FILE_EXT } from 'lib/constants'
test.describe('integrations tests', () => {
test(
'Creating a new file or switching file while in sketchMode should exit sketchMode',
{ tag: '@electron' },
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
if (runningOnWindows()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true })

View File

@ -1,5 +1,5 @@
import type { Locator, Page, Request, Route, TestInfo } from '@playwright/test'
import { expect } from '@playwright/test'
import type { Page, Locator, Route, Request } from '@playwright/test'
import { expect, TestInfo } from '@playwright/test'
import * as fs from 'fs'
import * as path from 'path'

View File

@ -1,12 +1,11 @@
import type { Locator, Page } from '@playwright/test'
import type { Page, Locator } from '@playwright/test'
import { expect } from '@playwright/test'
import {
checkIfPaneIsOpen,
closePane,
checkIfPaneIsOpen,
openPane,
sansWhitespace,
} from '@e2e/playwright/test-utils'
} from '../test-utils'
interface EditorState {
activeLines: Array<string>
@ -82,13 +81,6 @@ export class EditorFixture {
expectEditor = {
toContain: this._expectEditorToContain(),
not: { toContain: this._expectEditorToContain(true) },
toBe: async (code: string) => {
const currentCode = await this.getCurrentCode()
return expect(currentCode).toBe(code)
},
}
getCurrentCode = async () => {
return await this.codeContent.innerText()
}
snapshot = async (options?: { timeout?: number; name?: string }) => {
const wasPaneOpen = await this.checkIfPaneIsOpen()

View File

@ -1,28 +1,28 @@
/* eslint-disable react-hooks/rules-of-hooks */
import type {
BrowserContext,
ElectronApplication,
Page,
TestInfo,
Page,
} from '@playwright/test'
import { _electron as electron } from '@playwright/test'
import { SETTINGS_FILE_NAME } from '@src/lib/constants'
import type { DeepPartial } from '@src/lib/types'
import * as TOML from '@iarna/toml'
import { TEST_SETTINGS } from '../storageStates'
import { SETTINGS_FILE_NAME } from 'lib/constants'
import { getUtils, setup } from '../test-utils'
import fsp from 'fs/promises'
import fs from 'node:fs'
import path from 'path'
import type { Settings } from '@rust/kcl-lib/bindings/Settings'
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import { TEST_SETTINGS } from '@e2e/playwright/storageStates'
import { getUtils, settingsToToml, setup } from '@e2e/playwright/test-utils'
import { CmdBarFixture } from './cmdBarFixture'
import { EditorFixture } from './editorFixture'
import { ToolbarFixture } from './toolbarFixture'
import { SceneFixture } from './sceneFixture'
import { HomePageFixture } from './homePageFixture'
import { DeepPartial } from 'lib/types'
import { Settings } from '@rust/kcl-lib/bindings/Settings'
export class AuthenticatedApp {
public readonly page: Page
@ -124,7 +124,6 @@ export class ElectronZoo {
// We need to expose this in order for some tests that require folder
// creation and some code below.
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this
const options = {
@ -177,25 +176,6 @@ export class ElectronZoo {
this.context = this.electron.context()
await this.context.tracing.start({ screenshots: true, snapshots: true })
// We need to patch this because addInitScript will bind too late in our
// electron tests, never running. We need to call reload() after each call
// to guarantee it runs.
const oldContextAddInitScript = this.context.addInitScript
this.context.addInitScript = async function (a, b) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldContextAddInitScript.apply(this, [a, b])
await that.page.reload()
}
const oldPageAddInitScript = this.page.addInitScript
this.page.addInitScript = async function (a: any, b: any) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldPageAddInitScript.apply(this, [a, b])
await that.page.reload()
}
}
await this.context.tracing.startChunk()
@ -238,6 +218,26 @@ export class ElectronZoo {
}))
}
// We need to patch this because addInitScript will bind too late in our
// electron tests, never running. We need to call reload() after each call
// to guarantee it runs.
const oldContextAddInitScript = this.context.addInitScript
this.context.addInitScript = async function (a, b) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldContextAddInitScript.apply(this, [a, b])
await that.page.reload()
}
// No idea why we mix and match page and context's addInitScript but we do
const oldPageAddInitScript = this.page.addInitScript
this.page.addInitScript = async function (a: any, b: any) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldPageAddInitScript.apply(this, [a, b])
await that.page.reload()
}
if (!this.firstUrl) {
await this.page.getByText('Your Projects').count()
this.firstUrl = this.page.url()
@ -286,30 +286,26 @@ export class ElectronZoo {
let settingsOverridesToml = ''
if (appSettings) {
settingsOverridesToml = settingsToToml({
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
project_directory: this.projectDirName,
...appSettings.app,
},
project: {
...TEST_SETTINGS.project,
directory: this.projectDirName,
},
},
})
} else {
settingsOverridesToml = settingsToToml({
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
app: {
...TEST_SETTINGS.app,
},
project: {
...TEST_SETTINGS.project,
directory: this.projectDirName,
project_directory: this.projectDirName,
},
},
})

View File

@ -1,4 +1,4 @@
import type { Locator, Page } from '@playwright/test'
import type { Page, Locator } from '@playwright/test'
import { expect } from '@playwright/test'
interface ProjectCardState {
@ -96,7 +96,7 @@ export class HomePageFixture {
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
}
createAndGoToProject = async (projectTitle = 'untitled') => {
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
await this.projectsLoaded()
await this.projectButtonNew.click()
await this.projectTextName.click()

View File

@ -1,17 +1,15 @@
import type { Locator, Page } from '@playwright/test'
import { isArray, uuidv4 } from '@src/lib/utils'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { Page, Locator } from '@playwright/test'
import { expect } from '../zoo-test'
import { isArray, uuidv4 } from 'lib/utils'
import { CmdBarFixture } from './cmdBarFixture'
import {
closeDebugPanel,
doAndWaitForImageDiff,
getPixelRGBs,
getUtils,
openAndClearDebugPanel,
sendCustomCmd,
} from '@e2e/playwright/test-utils'
import { expect } from '@e2e/playwright/zoo-test'
getUtils,
} from '../test-utils'
type MouseParams = {
pixelDiff?: number

View File

@ -1,18 +1,14 @@
import { type Locator, type Page, test } from '@playwright/test'
import type { SidebarType } from '@src/components/ModelingSidebar/ModelingPanes'
import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants'
import type { ToolbarModeName } from '@src/lib/toolbar'
import { type Page, type Locator, test } from '@playwright/test'
import { expect } from '../zoo-test'
import {
checkIfPaneIsOpen,
closePane,
doAndWaitForImageDiff,
openPane,
} from '@e2e/playwright/test-utils'
import { expect } from '@e2e/playwright/zoo-test'
import { type baseUnitLabels } from '@src/lib/settings/settingsTypes'
type LengthUnitLabel = (typeof baseUnitLabels)[keyof typeof baseUnitLabels]
} from '../test-utils'
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
import { ToolbarModeName } from 'lib/toolbar'
export class ToolbarFixture {
public page: Page
@ -185,14 +181,6 @@ export class ToolbarFixture {
).toBeVisible()
await this.page.getByTestId('dropdown-center-rectangle').click()
}
selectBoolean = async (operation: 'union' | 'subtract' | 'intersect') => {
await this.page
.getByRole('button', { name: 'caret down Union: open menu' })
.click()
const operationTestId = `dropdown-boolean-${operation}`
await expect(this.page.getByTestId(operationTestId)).toBeVisible()
await this.page.getByTestId(operationTestId).click()
}
selectCircleThreePoint = async () => {
await this.page
@ -239,12 +227,6 @@ export class ToolbarFixture {
async checkIfFeatureTreePaneIsOpen() {
return this.checkIfPaneIsOpen(this.featureTreeId)
}
async selectUnit(unit: LengthUnitLabel) {
await this.page.getByTestId('units-menu').click()
const optionLocator = this.page.getByRole('button', { name: unit })
await expect(optionLocator).toBeVisible()
await optionLocator.click()
}
/**
* Get a specific operation button from the Feature Tree pane.

View File

@ -1,120 +0,0 @@
import { expect, test } from '@e2e/playwright/zoo-test'
import * as fsp from 'fs/promises'
import path from 'path'
test.describe('Import UI tests', () => {
test('shows toast when trying to sketch on imported face', async ({
context,
page,
homePage,
toolbar,
scene,
editor,
}) => {
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, 'import-test')
await fsp.mkdir(projectDir, { recursive: true })
// Create the imported file
await fsp.writeFile(
path.join(projectDir, 'toBeImported.kcl'),
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([281.54, 305.81], sketch001)
|> angledLine([0, 123.43], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
85.99
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude(profile001, length = 100)`
)
// Create the main file that imports
await fsp.writeFile(
path.join(projectDir, 'main.kcl'),
`import "toBeImported.kcl" as importedCube
importedCube
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([-134.53, -56.17], sketch001)
|> angledLine([0, 79.05], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
76.28
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(profile001, length = 100)
sketch003 = startSketchOn(extrude001, seg02)
sketch002 = startSketchOn(extrude001, seg01)`
)
})
await homePage.openProject('import-test')
await scene.waitForExecutionDone()
await scene.moveCameraTo(
{
x: -114,
y: -897,
z: 475,
},
{
x: -114,
y: -51,
z: 83,
}
)
const [_, hoverOverNonImport] = scene.makeMouseHelpers(611, 364)
const [importedFaceClick, hoverOverImported] = scene.makeMouseHelpers(
940,
150
)
await test.step('check code highlight works for code define in the file being edited', async () => {
await hoverOverNonImport()
await editor.expectState({
highlightedCode: 'startProfileAt([-134.53,-56.17],sketch001)',
diagnostics: [],
activeLines: ['import"toBeImported.kcl"asimportedCube'],
})
})
await test.step('check code does nothing when geometry is defined in an import', async () => {
await hoverOverImported()
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: ['import"toBeImported.kcl"asimportedCube'],
})
})
await test.step('check the user is warned when sketching on a imported face', async () => {
// Start sketch mode
await toolbar.startSketchPlaneSelection()
// Click on a face from the imported model
// await new Promise(() => {})
await importedFaceClick()
// Verify toast appears with correct content
await expect(page.getByText('This face is from an import')).toBeVisible()
await expect(
page.locator('.font-mono').getByText('toBeImported.kcl')
).toBeVisible()
await expect(
page.getByText('Please select this from the files pane to edit')
).toBeVisible()
})
})
})

View File

@ -257,29 +257,6 @@ export const isErrorWhitelisted = (exception: Error) => {
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
},
// TODO: fix this error in the code
{
name: 'ReferenceError',
message: '_testUtils is not defined',
stack: '',
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/snapshot-tests.spec.ts',
},
{
name: 'TypeError',
message: 'Failed to fetch',
stack: '',
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/snapshot-tests.spec.ts',
},
{
name: 'Error',
message: 'The "path" argument must be of type string. Received undefined',
stack:
'Error: The "path" argument must be of type string. Received undefined',
project: 'Google Chrome',
foundInSpec: '', // many tests are impacted by this error
},
]
const cleanString = (str: string) => str.replace(/[`"]/g, '')

View File

@ -1,7 +0,0 @@
export const throwError = (message: string): never => {
throw new Error(message)
}
export const throwTronAppMissing = () => {
throwError('tronApp is missing')
}

View File

@ -1,8 +1,7 @@
import fsp from 'fs/promises'
import { test, expect } from './zoo-test'
import { executorInputPath } from './test-utils'
import { join } from 'path'
import { executorInputPath } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import fsp from 'fs/promises'
test(
'When machine-api server not found butt is disabled and shows the reason',

View File

@ -1,294 +0,0 @@
import { PROJECT_SETTINGS_FILE_NAME } from '@src/lib/constants'
import * as fsp from 'fs/promises'
import { join } from 'path'
import type { NamedView } from '@rust/kcl-lib/bindings/NamedView'
import {
createProject,
perProjectsettingsToToml,
tomlToPerProjectSettings,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
// Helper function to determine if the file path on disk exists
// Specifically this is used to check if project.toml exists on disk
const fileExists = async (path: string) => {
return !!(await fsp
.stat(path)
.then((_) => true)
.catch((_) => false))
}
// Here are a few uuids.
// When created named views rust will auto generate uuids and they will
// never match the snapshots. Overwrite them in memory to these
// values to have them match the snapshots.
const uuid1: string = '0656fb1a-9640-473e-b334-591dc70c0138'
const uuid2: string = 'c810cf04-c6cc-4a4a-8b11-17bf445dcab7'
const uuid3: string = 'cfecbfee-48a6-4561-b96d-ffbe5678bb7d'
// Look up the named view by name and then rewrite it with the same uuid each time
const nameToUuid: Map<string, string> = new Map()
nameToUuid.set('uuid1', uuid1)
nameToUuid.set('uuid2', uuid2)
nameToUuid.set('uuid3', uuid3)
/**
* Given the project.toml string, overwrite the named views to be the constant uuid
* values to match the snapshots. The uuids are randomly generated
*/
function tomlStringOverWriteNamedViewUuids(toml: string): string {
const settings = tomlToPerProjectSettings(toml)
const namedViews = settings.settings?.app?.named_views
if (namedViews) {
const entries = Object.entries(namedViews)
const remappedNamedViews: { [key: string]: NamedView } = {}
entries.forEach(([_, value]) => {
if (value) {
// {name:'uuid1'} -> uuid1 lookup
const staticUuid = nameToUuid.get(value.name)
if (staticUuid) {
remappedNamedViews[staticUuid] = value
}
}
})
if (settings && settings.settings && settings.settings.app) {
settings.settings.app.named_views = remappedNamedViews
}
}
return perProjectsettingsToToml(settings)
}
test.describe('Named view tests', () => {
test('Verify project.toml is not created', async ({ page }, testInfo) => {
// Create project and load it
const projectName = 'named-views'
await createProject({ name: projectName, page })
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// project.toml should not exist on initial project creation
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(false)
})
test('Verify named view gets created', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView = 'uuid1'
// Create and load project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create named view
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
// Generate paths for the project.toml
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Expect project.toml to be generated on disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
})
test('Verify named view gets deleted', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView1 = 'uuid1'
const myNamedView2 = 'uuid2'
// Create project and go into the project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create a new named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView1)
await cmdBar.progressCmdBar(false)
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Except the project.toml to be written to disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
// Delete a named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('delete named view')
cmdBar.selectOption({ name: myNamedView2 })
await cmdBar.progressCmdBar(false)
// Read project.toml into memory again since we deleted a named view
tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// // Write the entire tomlString to a snapshot.
// // There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-deleted')
})
test('Verify named view gets loaded', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView = 'uuid1'
// Create project and go into the project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create a new named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Except the project.toml to be written to disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
// Create a load a named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('load named view')
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
// Check the toast appeared
await expect(
page.getByText(`Named view ${myNamedView} loaded.`)
).toBeVisible()
})
test('Verify two named views get created', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView1 = 'uuid1'
const myNamedView2 = 'uuid2'
// Create and load project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create named view
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView1)
await cmdBar.progressCmdBar(false)
await page.waitForTimeout(1000)
const orbitMouseStart = { x: 800, y: 130 }
const orbitMouseEnd = { x: 0, y: 130 }
await page.mouse.move(orbitMouseStart.x, orbitMouseStart.y)
await page.mouse.down({ button: 'middle' })
await page.mouse.move(orbitMouseEnd.x, orbitMouseEnd.y, {
steps: 3,
})
await page.mouse.up({ button: 'middle' })
await page.waitForTimeout(1000)
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView2)
await cmdBar.progressCmdBar(false)
// Wait a moment for the project.toml to get written to disk with the new view point
await page.waitForTimeout(1000)
// Generate paths for the project.toml
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Expect project.toml to be generated on disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-two-named-view-gets-created')
})
})

View File

@ -1,16 +0,0 @@
[settings]
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -1,16 +0,0 @@
[settings]
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -1,28 +0,0 @@
[settings]
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1
[settings.app.named_views.c810cf04-c6cc-4a4a-8b11-17bf445dcab7]
name = "uuid2"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 1_826.5239, 0.0, 0.0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,8 @@
// application, check it can make it to the project pane, and nothing more.
// It also tests our test wrappers are working.
// Additionally this serves as a nice minimal example.
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
test.describe('Open the application', () => {
test('see the project view', async ({ page, context }) => {

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