Compare commits
61 Commits
kcl-54
...
jtran/prom
Author | SHA1 | Date | |
---|---|---|---|
5ab814d153 | |||
fe83cd94ca | |||
5178a72c52 | |||
f4e801351c | |||
7329753211 | |||
7d46e7e271 | |||
0583eb07d5 | |||
73694563cf | |||
bb4ed59191 | |||
566143757f | |||
822f2ffc73 | |||
eb3ceba497 | |||
2c6d0621c9 | |||
d8e84cb5e3 | |||
0b1e79871f | |||
954fddf578 | |||
f94f748339 | |||
e2b7b22ca9 | |||
efc8c82d8b | |||
eac5abba79 | |||
1956c14b8a | |||
017fac7041 | |||
0bdc50c78f | |||
57d78b6094 | |||
db5ce7ba85 | |||
51c16d0048 | |||
6532b23f1c | |||
49304b9ecd | |||
358b34de4c | |||
9973e5fde3 | |||
36875e05fd | |||
42123383bb | |||
ef4c606ed1 | |||
1ebb73b935 | |||
b57d31c0e7 | |||
678ebbc310 | |||
cc2efd316c | |||
d1f811f91d | |||
7ca3afff9f | |||
71b9e40bd9 | |||
4f35197a96 | |||
40b0cf5fd3 | |||
355e6acf0d | |||
4ff38e7f44 | |||
1dcd3b84b7 | |||
2957216bd3 | |||
11160f0b40 | |||
4b2c745db5 | |||
bb983021b1 | |||
d27b8871bc | |||
1753047d87 | |||
fa16fcedff | |||
0677474097 | |||
0c4826cdd5 | |||
b6fe660b84 | |||
c53fa421ad | |||
736533a482 | |||
a15565682d | |||
58861cd24a | |||
c0cdcb1b98 | |||
41f45afb3c |
@ -1,5 +1,6 @@
|
||||
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
|
||||
@ -8,3 +9,5 @@ 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
|
||||
|
10
.envrc
@ -1,3 +1,13 @@
|
||||
# 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 .
|
||||
|
31
.eslintrc
@ -20,8 +20,26 @@
|
||||
"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": "^_",
|
||||
@ -29,6 +47,13 @@
|
||||
"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/restrict-plus-operands": "error",
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
"jsx-a11y/no-autofocus": "off",
|
||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||
@ -37,7 +62,7 @@
|
||||
{
|
||||
"name": "isNaN",
|
||||
"message": "Use Number.isNaN() instead."
|
||||
},
|
||||
}
|
||||
],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
@ -51,7 +76,7 @@
|
||||
"never"
|
||||
],
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"suggest-no-throw/suggest-no-throw": "warn",
|
||||
"suggest-no-throw/suggest-no-throw": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@ -71,7 +96,7 @@
|
||||
"plugin:testing-library/react"
|
||||
],
|
||||
"rules": {
|
||||
"suggest-no-throw/suggest-no-throw": "off",
|
||||
"suggest-no-throw/suggest-no-throw": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
13
.github/workflows/e2e-tests.yml
vendored
@ -229,10 +229,6 @@ 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 }}
|
||||
|
||||
@ -257,7 +253,8 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Commit changes, if any
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
|
||||
# TODO: find a more reliable way to detect visual changes
|
||||
if: ${{ false && 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
|
||||
@ -343,7 +340,7 @@ jobs:
|
||||
run: yarn tronb:vite:dev
|
||||
|
||||
- name: Install vector
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && contains(matrix.os, 'ubuntu') }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
@ -376,11 +373,7 @@ jobs:
|
||||
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
|
||||
|
144
.github/workflows/static-analysis.yml
vendored
@ -28,43 +28,57 @@ jobs:
|
||||
- run: yarn fmt-check
|
||||
|
||||
yarn-build-wasm:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# Build the wasm blob once on the fastest runner.
|
||||
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
|
||||
- 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
|
||||
with:
|
||||
cache: false # Configured below.
|
||||
|
||||
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
|
||||
with:
|
||||
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
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './rust'
|
||||
|
||||
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
|
||||
with:
|
||||
tool: wasm-pack
|
||||
- run: yarn build:wasm
|
||||
- run: yarn tsc
|
||||
- name: Build Wasm
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
|
||||
yarn-lint:
|
||||
runs-on: ubuntu-22.04
|
||||
- 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
|
||||
@ -73,7 +87,53 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- run: yarn --cwd ./rust/kcl-language-server --modules-folder node_modules 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 tsc
|
||||
|
||||
yarn-lint:
|
||||
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 lint
|
||||
|
||||
python-codespell:
|
||||
@ -91,6 +151,7 @@ jobs:
|
||||
|
||||
yarn-unit-test-kcl-samples:
|
||||
runs-on: ubuntu-latest
|
||||
needs: yarn-build-wasm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -103,7 +164,22 @@ jobs:
|
||||
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
|
||||
with:
|
||||
tool: wasm-pack
|
||||
- run: yarn build:wasm
|
||||
|
||||
- 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 simpleserver:bg
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
@ -120,6 +196,7 @@ jobs:
|
||||
|
||||
yarn-unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: yarn-build-wasm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -132,7 +209,22 @@ jobs:
|
||||
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
|
||||
with:
|
||||
tool: wasm-pack
|
||||
- run: yarn build:wasm
|
||||
|
||||
- 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 simpleserver:bg
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
|
14
.github/workflows/update-e2e-branch.yml
vendored
@ -28,12 +28,18 @@ jobs:
|
||||
|
||||
- name: Sync with main
|
||||
run: |
|
||||
# checkout our branch
|
||||
# Create the branch
|
||||
git checkout all-e2e || git checkout -b all-e2e
|
||||
# fetch origin
|
||||
|
||||
# Reset to main
|
||||
git fetch origin
|
||||
# reset to main
|
||||
git reset --hard origin/main
|
||||
# force push it
|
||||
|
||||
# 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
|
||||
git push --force origin all-e2e
|
||||
|
4
Makefile
@ -37,7 +37,7 @@ build-web: public/kcl_wasm_lib_bg.wasm build/index.html
|
||||
build-desktop: public/kcl_wasm_lib_bg.wasm .vite/build/main.js
|
||||
|
||||
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES)$(RUST_SOURCES)
|
||||
yarn build:wasm
|
||||
yarn build:wasm:dev
|
||||
|
||||
build/index.html: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
|
||||
yarn build:local
|
||||
@ -99,7 +99,7 @@ test-e2e-web: install build-web ## Run the web e2e tests
|
||||
|
||||
.PHONY: test-e2e-desktop
|
||||
test-e2e-desktop: install build-desktop ## Run the desktop e2e tests
|
||||
yarn test:playwright:electron --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep=$(E2E_GREP)
|
||||
yarn test:playwright:electron --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep="$(E2E_GREP)"
|
||||
|
||||
###############################################################################
|
||||
# CLEAN
|
||||
|
@ -9,13 +9,9 @@ layout: manual
|
||||
|
||||
### `std`
|
||||
|
||||
- [`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)
|
||||
- [`YZ`](/docs/kcl/consts/std-YZ)
|
||||
- [`ZERO`](/docs/kcl/consts/std-ZERO)
|
||||
|
||||
### `std::math`
|
||||
|
||||
@ -23,3 +19,10 @@ 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)
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::HALF_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::HALF_TURN: number(deg) = 180deg
|
||||
```
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::QUARTER_TURN: number(deg) = 90deg
|
||||
```
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::THREE_QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::THREE_QUARTER_TURN: number(deg) = 270deg
|
||||
```
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::ZERO"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::ZERO: number = 0
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-turns-HALF_TURN.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::turns::HALF_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::turns::HALF_TURN: number(deg) = 180deg
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-turns-QUARTER_TURN.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::turns::QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::turns::QUARTER_TURN: number(deg) = 90deg
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-turns-THREE_QUARTER_TURN.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::turns::THREE_QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::turns::THREE_QUARTER_TURN: number(deg) = 270deg
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-turns-ZERO.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::turns::ZERO"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::turns::ZERO: number = 0
|
||||
```
|
||||
|
||||
|
@ -23,19 +23,15 @@ layout: manual
|
||||
* [`tag`](kcl/types/tag)
|
||||
* **std**
|
||||
* [`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)
|
||||
* [`THREE_QUARTER_TURN`](kcl/consts/std-THREE_QUARTER_TURN)
|
||||
* [`XY`](kcl/consts/std-XY)
|
||||
* [`XZ`](kcl/consts/std-XZ)
|
||||
* [`YZ`](kcl/consts/std-YZ)
|
||||
* [`ZERO`](kcl/consts/std-ZERO)
|
||||
* [`abs`](kcl/abs)
|
||||
* [`acos`](kcl/acos)
|
||||
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
|
||||
@ -146,3 +142,8 @@ layout: manual
|
||||
* [`tan`](kcl/std-math-tan)
|
||||
* **std::sketch**
|
||||
* [`circle`](kcl/std-sketch-circle)
|
||||
* **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)
|
||||
|
@ -6,6 +6,10 @@ layout: manual
|
||||
|
||||
Rotate a solid or a sketch.
|
||||
|
||||
This is really useful for assembling parts together. You can create a part and then rotate it to the correct orientation.
|
||||
|
||||
For sketches, you can use this to rotate a sketch and then loft it with another sketch.
|
||||
|
||||
### Using Roll, Pitch, and Yaw
|
||||
|
||||
When rotating a part in 3D space, "roll," "pitch," and "yaw" refer to the three rotational axes used to describe its orientation: roll is rotation around the longitudinal axis (front-to-back), pitch is rotation around the lateral axis (wing-to-wing), and yaw is rotation around the vertical axis (up-down); essentially, it's like tilting the part on its side (roll), tipping the nose up or down (pitch), and turning it left or right (yaw).
|
||||
@ -166,7 +170,7 @@ fn square() {
|
||||
profile001 = square()
|
||||
|
||||
profile002 = square()
|
||||
|> translate(translate = [0, 0, 20])
|
||||
|> translate(x = 0, y = 0, z = 20)
|
||||
|> rotate(axis = [0, 0, 1.0], angle = 45)
|
||||
|
||||
loft([profile001, profile002])
|
||||
|
@ -17,7 +17,7 @@ circle(@sketch_or_surface: Sketch | Plane | Face, center: Point2d, radius: numbe
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch_or_surface` | [`Sketch`](/docs/kcl/types/Sketch) `|` [`Plane`](/docs/kcl/types/Face) `|` [`Plane`](/docs/kcl/types/Face) | Sketch to extend, or plane or surface to sketch on. | Yes |
|
||||
| `sketch_or_surface` | [`Sketch`](/docs/kcl/types/Sketch) OR [`Plane`](/docs/kcl/types/Plane) OR [`Face`](/docs/kcl/types/Face) | Sketch to extend, or plane or surface to sketch on. | Yes |
|
||||
| `center` | [`Point2d`](/docs/kcl/types/Point2d) | The center of the circle. | Yes |
|
||||
| `radius` | [`number`](/docs/kcl/types/number) | The radius of the circle. | Yes |
|
||||
| [`tag`](/docs/kcl/types/tag) | [`tag`](/docs/kcl/types/tag) | Create a new tag which refers to this circle. | No |
|
||||
|
6814
docs/kcl/std.json
@ -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` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
|
||||
| `sourceRange` |`[integer, integer, integer]`| 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` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
|
||||
| `sourceRange` |`[integer, integer, integer]`| 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` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
|
||||
| `sourceRange` |`[integer, integer, integer]`| 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` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
|
||||
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -17,6 +17,6 @@ Geometry metadata.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
|
||||
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
|
||||
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ A helix.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `value` |[`string`](/docs/kcl/types/string)| The id of the helix. | No |
|
||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
||||
| `artifactId` |[`string`](/docs/kcl/types/string)| 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 |
|
||||
|
@ -285,7 +285,7 @@ Data for an imported geometry.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Module`| | No |
|
||||
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No |
|
||||
| `value` |`integer`| Identifier of a source file. Uses a u32 to keep the size small. | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -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` |[`TyF64`](/docs/kcl/types/TyF64)| The length of the line. | No |
|
||||
| `length` |[`number`](/docs/kcl/types/number)| The length of the line. | No |
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ A sketch type.
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `plane`| | No |
|
||||
| `id` |[`string`](/docs/kcl/types/string)| The id of the plane. | No |
|
||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
||||
| `artifactId` |[`string`](/docs/kcl/types/string)| 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` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
||||
| `artifactId` |[`string`](/docs/kcl/types/string)| 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 |
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "SourceRange"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `integer` (`uint`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -5,17 +5,11 @@ layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
**Type:** [`number`](/docs/kcl/types/number) (`double`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `n` |[`number`](/docs/kcl/types/number)| | No |
|
||||
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No |
|
||||
|
||||
|
||||
|
@ -85,7 +85,7 @@ async function doBasicSketch(
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
|
||||
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(length = ${commonPoints.num1})
|
||||
@ -119,10 +119,7 @@ async function doBasicSketch(
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(
|
||||
await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)
|
||||
).toBeLessThan(3)
|
||||
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
|
||||
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
|
||||
}
|
||||
|
||||
// hold down shift
|
||||
@ -145,7 +142,7 @@ async function doBasicSketch(
|
||||
// Open the code pane.
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
|
||||
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(length = ${commonPoints.num1}, tag = $seg01)
|
||||
|
117
e2e/playwright/boolean.spec.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
@ -46,7 +46,7 @@ test.describe(
|
||||
},
|
||||
}
|
||||
|
||||
const code = `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()
|
||||
|
||||
|
@ -21,14 +21,15 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`// 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)`
|
||||
`@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)`
|
||||
)
|
||||
})
|
||||
|
||||
@ -250,11 +251,11 @@ test(
|
||||
])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
|
@ -513,7 +513,8 @@ 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
|
||||
await scene.settled(cmdBar)
|
||||
// Don't use scene.settled here
|
||||
await expect(scene.startEditSketchBtn).toBeEnabled({ timeout: 15_000 })
|
||||
|
||||
await test.step(`Create a parameter via command bar`, async () => {
|
||||
await cmdBar.cmdBarOpenBtn.click()
|
||||
@ -542,7 +543,12 @@ 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({
|
||||
|
@ -20,11 +20,11 @@ test(
|
||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(bracketDir, 'other.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
@ -107,7 +107,7 @@ test(
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBeGreaterThan(300_000)
|
||||
.toBeGreaterThan(30_000)
|
||||
})
|
||||
})
|
||||
|
||||
@ -187,7 +187,7 @@ test(
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBeGreaterThan(70_000)
|
||||
.toBeGreaterThan(50_000)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -32,26 +32,30 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn(XY)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line(end = [20, 0])
|
||||
|> line(end = [0, 20])
|
||||
|> line(end = [-20, 0])
|
||||
// |> close()`)
|
||||
// |> close()`.replaceAll('\n', '')
|
||||
)
|
||||
|
||||
// uncomment the code
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn(XY)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line(end = [20, 0])
|
||||
|> line(end = [0, 20])
|
||||
|> line(end = [-20, 0])
|
||||
|> close()`)
|
||||
|> close()`.replaceAll('\n', '')
|
||||
)
|
||||
})
|
||||
|
||||
test('ensure we use the cache, and do not re-execute', async ({
|
||||
@ -178,13 +182,15 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
await page.locator('#code-pane button:first-child').click()
|
||||
await page.locator('button:has-text("Format code")').click()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn(XY)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line(end = [20, 0])
|
||||
|> line(end = [0, 20])
|
||||
|> line(end = [-20, 0])
|
||||
|> close()`)
|
||||
|> close()`.replaceAll('\n', '')
|
||||
)
|
||||
})
|
||||
|
||||
test('if you click the format button it formats your code and executes so lints are still there', async ({
|
||||
@ -227,13 +233,15 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch_001 = startSketchOn(XY)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch_001 = startSketchOn(XY)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line(end = [20, 0])
|
||||
|> line(end = [0, 20])
|
||||
|> line(end = [-20, 0])
|
||||
|> close()`)
|
||||
|> close()`.replaceAll('\n', '')
|
||||
)
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
@ -471,6 +479,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
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 })
|
||||
@ -490,10 +499,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
await page.keyboard.press('ArrowRight')
|
||||
|
||||
// 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)
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
@ -815,10 +821,12 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
// there shouldn't be any auto complete options for 'lin' in the comment
|
||||
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn(XZ)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(%, length = 5) // lin`)
|
||||
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
|
||||
)
|
||||
|
||||
// expect there to be no KCL errors
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
|
||||
@ -888,10 +896,12 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
// there shouldn't be any auto complete options for 'lin' in the comment
|
||||
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn(XZ)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(%, length = 5) // lin`)
|
||||
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
|
||||
)
|
||||
})
|
||||
})
|
||||
test('Can undo a click and point extrude with ctrl+z', async ({
|
||||
@ -1206,4 +1216,55 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -81,6 +81,13 @@ 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()
|
||||
|
@ -124,6 +124,7 @@ 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 = {
|
||||
|
@ -310,7 +310,9 @@ export async function expectPixelColor(
|
||||
.toBeTruthy()
|
||||
.catch((cause) => {
|
||||
throw new Error(
|
||||
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
||||
`ExpectPixelColor: point ${JSON.stringify(
|
||||
coords
|
||||
)} was expecting ${colour} but got ${finalValue}`,
|
||||
{ cause }
|
||||
)
|
||||
})
|
||||
|
@ -181,6 +181,14 @@ 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
|
||||
|
@ -257,6 +257,29 @@ 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, '')
|
||||
|
@ -11,7 +11,7 @@ test(
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
@ -51,7 +51,7 @@ test(
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
|
292
e2e/playwright/named-views.spec.ts
Normal file
@ -0,0 +1,292 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import {
|
||||
createProject,
|
||||
tomlToPerProjectSettings,
|
||||
perProjectsettingsToToml,
|
||||
} from './test-utils'
|
||||
import { NamedView } from '@rust/kcl-lib/bindings/NamedView'
|
||||
|
||||
// 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')
|
||||
})
|
||||
})
|
@ -0,0 +1,16 @@
|
||||
[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
|
@ -0,0 +1,16 @@
|
||||
[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
|
@ -0,0 +1,28 @@
|
||||
[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
|
312
e2e/playwright/native-file-menu.spec.ts
Normal file
@ -0,0 +1,312 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
|
||||
/**
|
||||
* Not all menu actions are tested. Some are default electron menu actions.
|
||||
* Test file menu actions that trigger something in the frontend
|
||||
*/
|
||||
test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
||||
test.describe('Home page', () => {
|
||||
test.describe('File role', () => {
|
||||
test('File.Create project', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const newProject =
|
||||
app.applicationMenu.getMenuItemById('File.New project')
|
||||
if (!newProject) fail()
|
||||
newProject.click()
|
||||
})
|
||||
// Check that the command bar is opened
|
||||
await expect(cmdBar.cmdBarElement).toBeVisible()
|
||||
// Check the placeholder project name exists
|
||||
const actualArgument = await cmdBar.cmdBarElement
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.inputValue()
|
||||
const expectedArgument = 'project-$nnn'
|
||||
expect(actualArgument).toBe(expectedArgument)
|
||||
})
|
||||
test('File.Open project', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const openProject =
|
||||
app.applicationMenu.getMenuItemById('File.Open project')
|
||||
if (!openProject) fail()
|
||||
openProject.click()
|
||||
})
|
||||
// Check that the command bar is opened
|
||||
await expect(cmdBar.cmdBarElement).toBeVisible()
|
||||
// Check the placeholder project name exists
|
||||
const actual = await cmdBar.cmdBarElement
|
||||
.getByTestId('command-name')
|
||||
.textContent()
|
||||
const expected = 'Open project'
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
test('File.Preferences.User settings', async ({
|
||||
tronApp,
|
||||
cmdBar,
|
||||
page,
|
||||
}) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const userSettings = app.applicationMenu.getMenuItemById(
|
||||
'File.Preferences.User settings'
|
||||
)
|
||||
if (!userSettings) fail()
|
||||
userSettings.click()
|
||||
})
|
||||
const settings = page.getByTestId('settings-dialog-panel')
|
||||
await expect(settings).toBeVisible()
|
||||
// You are viewing the user tab
|
||||
const actualText = settings.getByText(
|
||||
'The overall appearance of the app'
|
||||
)
|
||||
await expect(actualText).toBeVisible()
|
||||
})
|
||||
test('File.Preferences.Keybindings', async ({
|
||||
tronApp,
|
||||
cmdBar,
|
||||
page,
|
||||
}) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const keybindings = app.applicationMenu.getMenuItemById(
|
||||
'File.Preferences.Keybindings'
|
||||
)
|
||||
if (!keybindings) fail()
|
||||
keybindings.click()
|
||||
})
|
||||
const settings = page.getByTestId('settings-dialog-panel')
|
||||
await expect(settings).toBeVisible()
|
||||
// You are viewing the keybindings tab
|
||||
const enterSketchMode = settings.locator('#enter-sketch-mode')
|
||||
await expect(enterSketchMode).toBeVisible()
|
||||
})
|
||||
test('File.Preferences.User default units', async ({
|
||||
tronApp,
|
||||
cmdBar,
|
||||
page,
|
||||
}) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'File.Preferences.User default units'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
const settings = page.getByTestId('settings-dialog-panel')
|
||||
await expect(settings).toBeVisible()
|
||||
const defaultUnit = settings.locator('#defaultUnit')
|
||||
await expect(defaultUnit).toBeVisible()
|
||||
})
|
||||
test('File.Preferences.Theme', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'File.Preferences.Theme'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
// Check that the command bar is opened
|
||||
await expect(cmdBar.cmdBarElement).toBeVisible()
|
||||
// Check the placeholder project name exists
|
||||
const actual = await cmdBar.cmdBarElement
|
||||
.getByTestId('command-name')
|
||||
.textContent()
|
||||
const expected = 'Settings · app · theme'
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
test('File.Preferences.Theme color', async ({
|
||||
tronApp,
|
||||
cmdBar,
|
||||
page,
|
||||
}) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'File.Preferences.Theme color'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
const settings = page.getByTestId('settings-dialog-panel')
|
||||
await expect(settings).toBeVisible()
|
||||
const defaultUnit = settings.locator('#themeColor')
|
||||
await expect(defaultUnit).toBeVisible()
|
||||
})
|
||||
test('File.Preferences.Sign out', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById('File.Sign out')
|
||||
if (!menu) fail()
|
||||
// FIXME: Add back when you can actually sign out
|
||||
// menu.click()
|
||||
})
|
||||
// FIXME: When signing out during E2E the page is not bound correctly.
|
||||
// It cannot find the button
|
||||
// const signIn = page.getByTestId('sign-in-button')
|
||||
// await expect(signIn).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Edit role', () => {
|
||||
test('Edit.Rename project', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'Edit.Rename project'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
// Check the placeholder project name exists
|
||||
const actual = await cmdBar.cmdBarElement
|
||||
.getByTestId('command-name')
|
||||
.textContent()
|
||||
const expected = 'Rename project'
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
test('Edit.Delete project', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'Edit.Delete project'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
// Check the placeholder project name exists
|
||||
const actual = await cmdBar.cmdBarElement
|
||||
.getByTestId('command-name')
|
||||
.textContent()
|
||||
const expected = 'Delete project'
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
test('Edit.Change project directory', async ({
|
||||
tronApp,
|
||||
cmdBar,
|
||||
page,
|
||||
}) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'Edit.Change project directory'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
const settings = page.getByTestId('settings-dialog-panel')
|
||||
await expect(settings).toBeVisible()
|
||||
const projectDirectory = settings.locator('#projectDirectory')
|
||||
await expect(projectDirectory).toBeVisible()
|
||||
})
|
||||
})
|
||||
test.describe('View role', () => {
|
||||
test('View.Command Palette...', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'View.Command Palette...'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
// Check the placeholder project name exists
|
||||
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
|
||||
await expect(actual).toBeVisible()
|
||||
})
|
||||
})
|
||||
test.describe('Help role', () => {
|
||||
test('Help.Show all commands', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'Help.Show all commands'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
// Check the placeholder project name exists
|
||||
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
|
||||
await expect(actual).toBeVisible()
|
||||
})
|
||||
test('Help.KCL code samples', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'Help.KCL code samples'
|
||||
)
|
||||
if (!menu) fail()
|
||||
})
|
||||
})
|
||||
test('Help.Refresh and report a bug', async ({
|
||||
tronApp,
|
||||
cmdBar,
|
||||
page,
|
||||
}) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'Help.Refresh and report a bug'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
// Core dump and refresh magic number timeout
|
||||
await page.waitForTimeout(7000)
|
||||
const actual = page.getByText(
|
||||
'No Projects found, ready to make your first one?'
|
||||
)
|
||||
await expect(actual).toBeVisible()
|
||||
})
|
||||
test('Help.Reset onboarding', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) fail()
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'Help.Reset onboarding'
|
||||
)
|
||||
if (!menu) fail()
|
||||
menu.click()
|
||||
})
|
||||
|
||||
const actual = page.getByText(
|
||||
`This is a hardware design tool that lets you edit visually, with code, or both. It's powered by the KittyCAD Design API, the first API created for anyone to build hardware design tools.`
|
||||
)
|
||||
await expect(actual).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -230,9 +230,9 @@ test.describe('Onboarding tests', () => {
|
||||
|
||||
// Override beforeEach test setup
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
async ({ settingsKey, settings, code }) => {
|
||||
// Give some initial code, so we can test that it's cleared
|
||||
localStorage.setItem('persistCode', originalCode)
|
||||
localStorage.setItem('persistCode', code)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
@ -240,6 +240,7 @@ test.describe('Onboarding tests', () => {
|
||||
settings: settingsToToml({
|
||||
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||
}),
|
||||
code: originalCode,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -137,7 +137,7 @@ test.describe('Point-and-click tests', () => {
|
||||
|
||||
await scene.moveCameraTo(cameraPos, cameraTarget)
|
||||
|
||||
await test.step('check chamfer selection changes cursor positon', async () => {
|
||||
await test.step('check chamfer selection changes cursor position', async () => {
|
||||
await expect(async () => {
|
||||
// sometimes initial click doesn't register
|
||||
await clickChamfer()
|
||||
@ -173,7 +173,7 @@ test.describe('Point-and-click tests', () => {
|
||||
})
|
||||
await test.step('Check there is no errors after code created in previous steps executes', async () => {
|
||||
await editor.expectState({
|
||||
activeLines: ['sketch001 = startSketchOn(XZ)'],
|
||||
activeLines: ['@settings(defaultLengthUnit = in)'],
|
||||
highlightedCode: '',
|
||||
diagnostics: [],
|
||||
})
|
||||
@ -299,7 +299,8 @@ test.describe('Point-and-click tests', () => {
|
||||
|
||||
await test.step('verify at the end of the test that final code is what is expected', async () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
@ -369,7 +370,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|
||||
})
|
||||
})
|
||||
|
||||
test('Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array', async ({
|
||||
test('Works on chamfers that are not in a pipeExpression can break up multi edges in a chamfer array', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
@ -418,7 +419,8 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|
||||
|>close()`,
|
||||
})
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([75.8, 317.2], %)
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
@ -1082,8 +1084,8 @@ openSketch = startSketchOn(XY)
|
||||
}) => {
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 620, y: 257 }
|
||||
const expectedOutput = `helix001 = helix( revolutions = 1, angleStart = 360, ccw = false, radius = 5, axis = 'X', length = 5,)`
|
||||
const expectedLine = `revolutions=1,`
|
||||
const expectedOutput = `helix001 = helix( axis = 'X', radius = 5, length = 5, revolutions = 1, angleStart = 360, ccw = false,)`
|
||||
const expectedLine = `axis='X',`
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
@ -1091,17 +1093,16 @@ openSketch = startSketchOn(XY)
|
||||
await toolbar.helixButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'axisOrEdge',
|
||||
currentArgKey: 'mode',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Mode: '',
|
||||
AngleStart: '',
|
||||
AxisOrEdge: '',
|
||||
CounterClockWise: '',
|
||||
Length: '',
|
||||
Radius: '',
|
||||
Revolutions: '',
|
||||
Radius: '',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'axisOrEdge',
|
||||
highlightedHeaderArg: 'mode',
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -1110,7 +1111,19 @@ openSketch = startSketchOn(XY)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Mode: 'Axis',
|
||||
Axis: 'X',
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
Length: '5',
|
||||
Radius: '5',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
@ -1134,30 +1147,31 @@ openSketch = startSketchOn(XY)
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Helix',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: initialInput,
|
||||
currentArgKey: 'CounterClockWise',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Axis: 'X',
|
||||
CounterClockWise: '',
|
||||
Length: initialInput,
|
||||
Radius: '5',
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
Radius: '5',
|
||||
Length: initialInput,
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
highlightedHeaderArg: 'CounterClockWise',
|
||||
})
|
||||
await page.keyboard.press('Shift+Backspace')
|
||||
await expect(cmdBar.currentArgumentInput).toBeVisible()
|
||||
await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Axis: 'X',
|
||||
CounterClockWise: '',
|
||||
Length: newInput,
|
||||
Radius: '5',
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
Radius: '5',
|
||||
Length: newInput,
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
@ -1181,14 +1195,14 @@ openSketch = startSketchOn(XY)
|
||||
{
|
||||
selectionType: 'segment',
|
||||
testPoint: { x: 513, y: 221 },
|
||||
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = seg01, length = 100,)`,
|
||||
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = seg01, length = 50,)`,
|
||||
expectedOutput: `helix001 = helix( axis = seg01, radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
|
||||
expectedEditedOutput: `helix001 = helix( axis = seg01, radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
|
||||
},
|
||||
{
|
||||
selectionType: 'sweepEdge',
|
||||
testPoint: { x: 564, y: 364 },
|
||||
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = getOppositeEdge(seg01), length = 100,)`,
|
||||
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = getOppositeEdge(seg01), length = 50,)`,
|
||||
expectedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
|
||||
expectedEditedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
|
||||
},
|
||||
]
|
||||
helixCases.map(
|
||||
@ -1225,17 +1239,16 @@ openSketch = startSketchOn(XY)
|
||||
await toolbar.helixButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'axisOrEdge',
|
||||
currentArgKey: 'mode',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
AngleStart: '',
|
||||
AxisOrEdge: '',
|
||||
Mode: '',
|
||||
CounterClockWise: '',
|
||||
Length: '',
|
||||
Radius: '',
|
||||
Revolutions: '',
|
||||
},
|
||||
highlightedHeaderArg: 'axisOrEdge',
|
||||
highlightedHeaderArg: 'mode',
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.selectOption({ name: 'Edge' }).click()
|
||||
@ -1246,21 +1259,17 @@ openSketch = startSketchOn(XY)
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.keyboard.insertText('0')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.selectOption({ name: 'True' }).click()
|
||||
await page.keyboard.insertText('1')
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.keyboard.insertText('100')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
AngleStart: '0',
|
||||
AxisOrEdge: 'Edge',
|
||||
Mode: 'Edge',
|
||||
Edge: `1 ${selectionType}`,
|
||||
CounterClockWise: '',
|
||||
Length: '100',
|
||||
Radius: '1',
|
||||
AngleStart: '0',
|
||||
Revolutions: '20',
|
||||
Radius: '1',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
@ -1280,22 +1289,24 @@ openSketch = startSketchOn(XY)
|
||||
0
|
||||
)
|
||||
await operationButton.dblclick()
|
||||
const initialInput = '100'
|
||||
const newInput = '50'
|
||||
const initialInput = '1'
|
||||
const newInput = '5'
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Helix',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: initialInput,
|
||||
currentArgKey: 'CounterClockWise',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
AngleStart: '0',
|
||||
CounterClockWise: '',
|
||||
Length: initialInput,
|
||||
Radius: '1',
|
||||
Revolutions: '20',
|
||||
Radius: initialInput,
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
highlightedHeaderArg: 'CounterClockWise',
|
||||
})
|
||||
await page
|
||||
.getByRole('button', { name: 'radius', exact: false })
|
||||
.click()
|
||||
await expect(cmdBar.currentArgumentInput).toBeVisible()
|
||||
await cmdBar.currentArgumentInput
|
||||
.locator('.cm-content')
|
||||
@ -1305,10 +1316,9 @@ openSketch = startSketchOn(XY)
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
AngleStart: '0',
|
||||
CounterClockWise: '',
|
||||
Length: newInput,
|
||||
Radius: '1',
|
||||
Revolutions: '20',
|
||||
Radius: newInput,
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
@ -1336,6 +1346,140 @@ openSketch = startSketchOn(XY)
|
||||
}
|
||||
)
|
||||
|
||||
test('Helix point-and-click on cylinder', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn(XY)
|
||||
profile001 = circle(
|
||||
sketch001,
|
||||
center = [0, 0],
|
||||
radius = 100,
|
||||
tag = $seg01,
|
||||
)
|
||||
extrude001 = extrude(profile001, length = 100)
|
||||
`
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 620, y: 257 }
|
||||
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const expectedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = false,)`
|
||||
const expectedLine = `cylinder = extrude001,`
|
||||
const expectedEditedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = true,)`
|
||||
|
||||
await test.step(`Go through the command bar flow`, async () => {
|
||||
await toolbar.helixButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'mode',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Mode: '',
|
||||
AngleStart: '',
|
||||
Revolutions: '',
|
||||
Radius: '',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'mode',
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.selectOption({ name: 'Cylinder' }).click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'cylinder',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Mode: 'Cylinder',
|
||||
Cylinder: '',
|
||||
AngleStart: '',
|
||||
Revolutions: '',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'cylinder',
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await clickOnWall()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Mode: 'Cylinder',
|
||||
Cylinder: '1 face',
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(expectedOutput)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [expectedLine],
|
||||
highlightedCode: '',
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Edit helix through the feature tree`, async () => {
|
||||
await editor.closePane()
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
|
||||
await operationButton.dblclick()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Helix',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'CounterClockWise',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'CounterClockWise',
|
||||
})
|
||||
await cmdBar.selectOption({ name: 'True' }).click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
CounterClockWise: 'true',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await toolbar.closePane('feature-tree')
|
||||
await toolbar.openPane('code')
|
||||
await editor.expectEditor.toContain(expectedEditedOutput)
|
||||
await editor.closePane()
|
||||
})
|
||||
|
||||
await test.step('Delete helix via feature tree selection', async () => {
|
||||
await toolbar.openPane('feature-tree')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Delete')
|
||||
await toolbar.closePane('feature-tree')
|
||||
await toolbar.openPane('code')
|
||||
await editor.expectEditor.not.toContain(expectedEditedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
const loftPointAndClickCases = [
|
||||
{ shouldPreselect: true },
|
||||
{ shouldPreselect: false },
|
||||
@ -1497,9 +1641,10 @@ loft001 = loft([sketch001, sketch002])
|
||||
{
|
||||
targetType: 'circle',
|
||||
testPoint: { x: 700, y: 250 },
|
||||
initialCode: `sketch001 = startSketchOn('YZ')
|
||||
initialCode: `@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(YZ)
|
||||
profile001 = circle(sketch001, center = [0, 0], radius = 500)
|
||||
sketch002 = startSketchOn('XZ')
|
||||
sketch002 = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> xLine(length = -500)
|
||||
|> tangentialArcTo([-2000, 500], %)`,
|
||||
@ -1507,7 +1652,8 @@ sketch002 = startSketchOn('XZ')
|
||||
{
|
||||
targetType: 'rectangle',
|
||||
testPoint: { x: 710, y: 255 },
|
||||
initialCode: `sketch001 = startSketchOn('YZ')
|
||||
initialCode: `@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(YZ)
|
||||
profile001 = startProfileAt([-400, -400], sketch001)
|
||||
|> angledLine([0, 800], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
@ -1520,7 +1666,7 @@ profile001 = startProfileAt([-400, -400], sketch001)
|
||||
], %)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
sketch002 = startSketchOn('XZ')
|
||||
sketch002 = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> xLine(length = -500)
|
||||
|> tangentialArcTo([-2000, 500], %)`,
|
||||
@ -1664,7 +1810,8 @@ sketch002 = startSketchOn('XZ')
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn(YZ)
|
||||
const initialCode = `@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(YZ)
|
||||
|> circle(
|
||||
center = [0, 0],
|
||||
radius = 500
|
||||
@ -1773,7 +1920,7 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
const filletColor: [number, number, number] = [127, 127, 127]
|
||||
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||
const lowTolerance = 20
|
||||
const highTolerance = 40
|
||||
const highTolerance = 70 // TODO: understand why I needed that for edgeColorYellow on macos (local)
|
||||
|
||||
// Setup
|
||||
await test.step(`Initial test setup`, async () => {
|
||||
@ -1860,6 +2007,54 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
await scene.expectPixelColor(filletColor, firstEdgeLocation, lowTolerance)
|
||||
})
|
||||
|
||||
// Test 1.1: Edit fillet (segment type)
|
||||
async function editFillet(
|
||||
featureTreeIndex: number,
|
||||
oldValue: string,
|
||||
newValue: string
|
||||
) {
|
||||
await toolbar.openPane('feature-tree')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||
'Fillet',
|
||||
featureTreeIndex
|
||||
)
|
||||
await operationButton.dblclick({ button: 'left' })
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Fillet',
|
||||
currentArgKey: 'radius',
|
||||
currentArgValue: oldValue,
|
||||
headerArguments: {
|
||||
Radius: oldValue,
|
||||
},
|
||||
highlightedHeaderArg: 'radius',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await page.keyboard.insertText(newValue)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Radius: newValue,
|
||||
},
|
||||
commandName: 'Fillet',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await toolbar.closePane('feature-tree')
|
||||
}
|
||||
|
||||
await test.step('Edit fillet via feature tree selection works', async () => {
|
||||
const firstFilletFeatureTreeIndex = 0
|
||||
const editedRadius = '1'
|
||||
await editFillet(firstFilletFeatureTreeIndex, '5', editedRadius)
|
||||
await editor.expectEditor.toContain(
|
||||
firstFilletDeclaration.replace('radius = 5', 'radius = ' + editedRadius)
|
||||
)
|
||||
|
||||
// Edit back to original radius
|
||||
await editFillet(firstFilletFeatureTreeIndex, editedRadius, '5')
|
||||
await editor.expectEditor.toContain(firstFilletDeclaration)
|
||||
})
|
||||
|
||||
// Test 2: Command bar flow without preselected edges
|
||||
await test.step(`Open fillet UI without selecting edges`, async () => {
|
||||
await page.waitForTimeout(100)
|
||||
@ -1944,6 +2139,23 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
)
|
||||
})
|
||||
|
||||
// Test 2.1: Edit fillet (edgeSweep type)
|
||||
await test.step('Edit fillet via feature tree selection works', async () => {
|
||||
const secondFilletFeatureTreeIndex = 1
|
||||
const editedRadius = '2'
|
||||
await editFillet(secondFilletFeatureTreeIndex, '5', editedRadius)
|
||||
await editor.expectEditor.toContain(
|
||||
secondFilletDeclaration.replace(
|
||||
'radius = 5',
|
||||
'radius = ' + editedRadius
|
||||
)
|
||||
)
|
||||
|
||||
// Edit back to original radius
|
||||
await editFillet(secondFilletFeatureTreeIndex, editedRadius, '5')
|
||||
await editor.expectEditor.toContain(secondFilletDeclaration)
|
||||
})
|
||||
|
||||
// Test 3: Delete fillets
|
||||
await test.step('Delete fillet via feature tree selection', async () => {
|
||||
await test.step('Open Feature Tree Pane', async () => {
|
||||
@ -1966,6 +2178,43 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
})
|
||||
})
|
||||
|
||||
test(`Fillet point-and-click edit rejected when not in pipe`, async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn(XY)
|
||||
profile001 = circle(
|
||||
sketch001,
|
||||
center = [0, 0],
|
||||
radius = 100,
|
||||
tag = $seg01,
|
||||
)
|
||||
extrude001 = extrude(profile001, length = 100)
|
||||
fillet001 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
|
||||
`
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
await test.step('Double-click in feature tree and expect error toast', async () => {
|
||||
await toolbar.openPane('feature-tree')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Fillet', 0)
|
||||
await operationButton.dblclick({ button: 'left' })
|
||||
await expect(
|
||||
page.getByText(
|
||||
'Only chamfer and fillet in pipe expressions are supported for edits'
|
||||
)
|
||||
).toBeVisible()
|
||||
await page.waitForTimeout(1000)
|
||||
})
|
||||
})
|
||||
|
||||
test(`Fillet point-and-click delete`, async ({
|
||||
context,
|
||||
page,
|
||||
@ -2231,7 +2480,8 @@ extrude001 = extrude(profile001, length = 5)
|
||||
cmdBar,
|
||||
}) => {
|
||||
// Code samples
|
||||
const initialCode = `sketch001 = startSketchOn(XY)
|
||||
const initialCode = `@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> startProfileAt([-12, -6], %)
|
||||
|> line(end = [0, 12])
|
||||
|> line(end = [24, 0])
|
||||
@ -2262,7 +2512,7 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
const chamferColor: [number, number, number] = [168, 168, 168]
|
||||
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||
const lowTolerance = 20
|
||||
const highTolerance = 40
|
||||
const highTolerance = 70 // TODO: understand why I needed that for edgeColorYellow on macos (local)
|
||||
|
||||
// Setup
|
||||
await test.step(`Initial test setup`, async () => {
|
||||
@ -2344,6 +2594,57 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
)
|
||||
})
|
||||
|
||||
// Test 1.1: Edit sweep
|
||||
async function editChamfer(
|
||||
featureTreeIndex: number,
|
||||
oldValue: string,
|
||||
newValue: string
|
||||
) {
|
||||
await toolbar.openPane('feature-tree')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||
'Chamfer',
|
||||
featureTreeIndex
|
||||
)
|
||||
await operationButton.dblclick({ button: 'left' })
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Chamfer',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: oldValue,
|
||||
headerArguments: {
|
||||
Length: oldValue,
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await page.keyboard.insertText(newValue)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Length: newValue,
|
||||
},
|
||||
commandName: 'Chamfer',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await toolbar.closePane('feature-tree')
|
||||
}
|
||||
|
||||
await test.step('Edit chamfer via feature tree selection works', async () => {
|
||||
const firstChamferFeatureTreeIndex = 0
|
||||
const editedLength = '1'
|
||||
await editChamfer(firstChamferFeatureTreeIndex, '5', editedLength)
|
||||
await editor.expectEditor.toContain(
|
||||
firstChamferDeclaration.replace(
|
||||
'length = 5',
|
||||
'length = ' + editedLength
|
||||
)
|
||||
)
|
||||
|
||||
// Edit back to original radius
|
||||
await editChamfer(firstChamferFeatureTreeIndex, editedLength, '5')
|
||||
await editor.expectEditor.toContain(firstChamferDeclaration)
|
||||
})
|
||||
|
||||
// Test 2: Command bar flow without preselected edges
|
||||
await test.step(`Open chamfer UI without selecting edges`, async () => {
|
||||
await page.waitForTimeout(100)
|
||||
@ -2428,6 +2729,23 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
)
|
||||
})
|
||||
|
||||
// Test 2.1: Edit chamfer (edgeSweep type)
|
||||
await test.step('Edit chamfer via feature tree selection works', async () => {
|
||||
const secondChamferFeatureTreeIndex = 1
|
||||
const editedLength = '2'
|
||||
await editChamfer(secondChamferFeatureTreeIndex, '5', editedLength)
|
||||
await editor.expectEditor.toContain(
|
||||
secondChamferDeclaration.replace(
|
||||
'length = 5',
|
||||
'length = ' + editedLength
|
||||
)
|
||||
)
|
||||
|
||||
// Edit back to original length
|
||||
await editChamfer(secondChamferFeatureTreeIndex, editedLength, '5')
|
||||
await editor.expectEditor.toContain(secondChamferDeclaration)
|
||||
})
|
||||
|
||||
// Test 3: Delete chamfer via feature tree selection
|
||||
await test.step('Open Feature Tree Pane', async () => {
|
||||
await toolbar.openPane('feature-tree')
|
||||
@ -2455,7 +2773,8 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
toolbar,
|
||||
}) => {
|
||||
// Code samples
|
||||
const initialCode = `sketch001 = startSketchOn(XY)
|
||||
const initialCode = `@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> startProfileAt([-12, -6], %)
|
||||
|> line(end = [0, 12])
|
||||
|> line(end = [24, 0], tag = $seg02)
|
||||
@ -2609,7 +2928,8 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn(XZ)
|
||||
const initialCode = `@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> circle(center = [0, 0], radius = 30)
|
||||
extrude001 = extrude(sketch001, length = 30)
|
||||
`
|
||||
@ -2744,7 +3064,8 @@ extrude001 = extrude(sketch001, length = 30)
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn(XY)
|
||||
const initialCode = `@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> startProfileAt([-20, 20], %)
|
||||
|> xLine(length = 40)
|
||||
|> yLine(length = -60)
|
||||
@ -2862,7 +3183,8 @@ extrude001 = extrude(sketch001, length = 40)
|
||||
})
|
||||
|
||||
const shellSketchOnFacesCases = [
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> circle(center = [0, 0], radius = 100)
|
||||
|> extrude(length = 100)
|
||||
|
||||
@ -2870,7 +3192,8 @@ sketch002 = startSketchOn(sketch001, 'END')
|
||||
|> circle(center = [0, 0], radius = 50)
|
||||
|> extrude(length = 50)
|
||||
`,
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> circle(center = [0, 0], radius = 100)
|
||||
extrude001 = extrude(sketch001, length = 100)
|
||||
|
||||
@ -3153,6 +3476,39 @@ segAng(rectangleSegmentA002),
|
||||
|
||||
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = 'X')`
|
||||
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
|
||||
|
||||
// Edit flow
|
||||
const newAngle = '90'
|
||||
await toolbar.openPane('feature-tree')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||
'Revolve',
|
||||
0
|
||||
)
|
||||
await operationButton.dblclick({ button: 'left' })
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Revolve',
|
||||
currentArgKey: 'angle',
|
||||
currentArgValue: '360',
|
||||
headerArguments: {
|
||||
Angle: '360',
|
||||
},
|
||||
highlightedHeaderArg: 'angle',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await page.keyboard.insertText(newAngle)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Angle: newAngle,
|
||||
},
|
||||
commandName: 'Revolve',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await toolbar.closePane('feature-tree')
|
||||
await editor.expectEditor.toContain(
|
||||
newCodeToFind.replace('angle = 360', 'angle = ' + newAngle)
|
||||
)
|
||||
})
|
||||
test('revolve surface around edge from an extruded solid2d', async ({
|
||||
context,
|
||||
@ -3163,26 +3519,22 @@ segAng(rectangleSegmentA002),
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-102.57, 101.72], %)
|
||||
|> angledLine([0, 202.6], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
202.6
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
const initialCode = `sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-102.57, 101.72], %)
|
||||
|> angledLine([0, 202.6], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
202.6
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
extrude001 = extrude(sketch001, length = 50)
|
||||
sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
|
||||
|> circle(
|
||||
center = [-11.34, 10.0],
|
||||
radius = 8.69
|
||||
)
|
||||
|> circle(center = [-11.34, 10.0], radius = 8.69)
|
||||
`
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
@ -3200,9 +3552,49 @@ radius = 8.69
|
||||
const lineCodeToSelection = `|> angledLine([0, 202.6], %, $rectangleSegmentA001)`
|
||||
await page.getByText(lineCodeToSelection).click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = getOppositeEdge(rectangleSegmentA001)) `
|
||||
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
|
||||
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = rectangleSegmentA001)`
|
||||
await editor.expectEditor.toContain(newCodeToFind)
|
||||
|
||||
// Edit flow
|
||||
const newAngle = '180'
|
||||
await toolbar.openPane('feature-tree')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||
'Revolve',
|
||||
0
|
||||
)
|
||||
await operationButton.dblclick({ button: 'left' })
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Revolve',
|
||||
currentArgKey: 'angle',
|
||||
currentArgValue: '360',
|
||||
headerArguments: {
|
||||
Angle: '360',
|
||||
},
|
||||
highlightedHeaderArg: 'angle',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await page.keyboard.insertText(newAngle)
|
||||
await page.getByRole('button', { name: 'Create new variable' }).click()
|
||||
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
|
||||
'angle001'
|
||||
)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Angle: newAngle,
|
||||
},
|
||||
commandName: 'Revolve',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await toolbar.closePane('feature-tree')
|
||||
await editor.expectEditor.toContain('angle001 = ' + newAngle)
|
||||
await editor.expectEditor.toContain(
|
||||
newCodeToFind.replace('angle = 360', 'angle = angle001')
|
||||
)
|
||||
})
|
||||
test('revolve sketch circle around line segment from startProfileAt sketch', async ({
|
||||
context,
|
||||
@ -3213,26 +3605,22 @@ radius = 8.69
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `
|
||||
sketch002 = startSketchOn(XY)
|
||||
|> startProfileAt([-2.02, 1.79], %)
|
||||
|> xLine(length = 2.6)
|
||||
sketch001 = startSketchOn('-XY')
|
||||
|> startProfileAt([-0.48, 1.25], %)
|
||||
|> angledLine([0, 2.38], %, $rectangleSegmentA001)
|
||||
|> angledLine([segAng(rectangleSegmentA001) - 90, 2.4], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
extrude001 = extrude(sketch001, length = 5)
|
||||
sketch003 = startSketchOn(extrude001, 'START')
|
||||
|> circle(
|
||||
center = [-0.69, 0.56],
|
||||
radius = 0.28
|
||||
)
|
||||
const initialCode = `sketch002 = startSketchOn(XY)
|
||||
|> startProfileAt([-2.02, 1.79], %)
|
||||
|> xLine(length = 2.6)
|
||||
sketch001 = startSketchOn(-XY)
|
||||
|> startProfileAt([-0.48, 1.25], %)
|
||||
|> angledLine([0, 2.38], %, $rectangleSegmentA001)
|
||||
|> angledLine([segAng(rectangleSegmentA001) - 90, 2.4], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
extrude001 = extrude(sketch001, length = 5)
|
||||
sketch003 = startSketchOn(extrude001, 'START')
|
||||
|> circle(center = [-0.69, 0.56], radius = 0.28)
|
||||
`
|
||||
|
||||
await context.addInitScript((initialCode) => {
|
||||
@ -3251,9 +3639,44 @@ radius = 8.69
|
||||
const lineCodeToSelection = `|> xLine(length = 2.6)`
|
||||
await page.getByText(lineCodeToSelection).click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
const newCodeToFind = `revolve001 = revolve(sketch003, angle = 360, axis = seg01)`
|
||||
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
|
||||
|
||||
// Edit flow
|
||||
const newAngle = '270'
|
||||
await toolbar.openPane('feature-tree')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||
'Revolve',
|
||||
0
|
||||
)
|
||||
await operationButton.dblclick({ button: 'left' })
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Revolve',
|
||||
currentArgKey: 'angle',
|
||||
currentArgValue: '360',
|
||||
headerArguments: {
|
||||
Angle: '360',
|
||||
},
|
||||
highlightedHeaderArg: 'angle',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await page.keyboard.insertText(newAngle)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Angle: newAngle,
|
||||
},
|
||||
commandName: 'Revolve',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await toolbar.closePane('feature-tree')
|
||||
await editor.expectEditor.toContain(
|
||||
newCodeToFind.replace('angle = 360', 'angle = ' + newAngle)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -3266,7 +3689,8 @@ radius = 8.69
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn(XZ)
|
||||
const initialCode = `@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile001 = circle(
|
||||
sketch001,
|
||||
center = [0, 0],
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
createProject,
|
||||
getPlaywrightDownloadDir,
|
||||
orRunWhenFullSuiteEnabled,
|
||||
runningOnWindows,
|
||||
} from './test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
import fs from 'fs'
|
||||
@ -86,7 +87,7 @@ test(
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
@ -123,7 +124,7 @@ test(
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
const errorDir = path.join(dir, 'broken-code')
|
||||
@ -161,7 +162,7 @@ test(
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [110, 110, 110]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(20)
|
||||
@ -212,7 +213,7 @@ test(
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
const emptyDir = path.join(dir, 'empty')
|
||||
@ -247,7 +248,7 @@ test(
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
@ -289,7 +290,7 @@ test(
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
|
||||
@ -318,7 +319,7 @@ test(
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
@ -351,11 +352,14 @@ test(
|
||||
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
if (runningOnWindows()) {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
}
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
@ -389,7 +393,7 @@ test(
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
@ -439,7 +443,6 @@ test(
|
||||
await page.getByText('broken-code').click()
|
||||
|
||||
// Gotcha: You can not use scene.waitForExecutionDone() since the KCL code is going to fail
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
@ -469,12 +472,15 @@ test.describe('Can export from electron app', () => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
}
|
||||
if (runningOnWindows()) {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
}
|
||||
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
@ -506,7 +512,7 @@ test.describe('Can export from electron app', () => {
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
@ -547,7 +553,7 @@ test.describe('Can export from electron app', () => {
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBeGreaterThan(300_000)
|
||||
.toBeGreaterThan(50_000)
|
||||
|
||||
// clean up exported file
|
||||
await fsp.rm(filepath)
|
||||
@ -1328,6 +1334,9 @@ test(
|
||||
'Can load a file with CRLF line endings',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
if (runningOnWindows()) {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
}
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const routerTemplateDir = path.join(dir, 'router-template-slate')
|
||||
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||
@ -1497,7 +1506,12 @@ test(
|
||||
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await page.locator('.cm-content').fill(`sketch001 = startSketchOn(XZ)
|
||||
// The file should be prepopulated with the user's unit settings.
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
'@settings(defaultLengthUnit = in)'
|
||||
)
|
||||
|
||||
await page.locator('.cm-content').fill(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-87.4, 282.92], %)
|
||||
|> line(end = [324.07, 27.199], tag = $seg01)
|
||||
|> line(end = [118.328, -291.754])
|
||||
|
@ -4,9 +4,9 @@ import path from 'path'
|
||||
import * as fsp from 'fs/promises'
|
||||
import {
|
||||
getUtils,
|
||||
executorInputPath,
|
||||
TEST_COLORS,
|
||||
TestColor,
|
||||
executorInputPath,
|
||||
orRunWhenFullSuiteEnabled,
|
||||
} from './test-utils'
|
||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||
@ -331,7 +331,7 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`@settings(defaultLengthUnit = mm)
|
||||
sketch002 = startSketchOn('XY')
|
||||
sketch002 = startSketchOn(XY)
|
||||
profile002 = startProfileAt([72.24, -52.05], sketch002)
|
||||
|> angledLine([0, 181.26], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
@ -582,7 +582,7 @@ extrude002 = extrude(profile002, length = 150)
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
@ -619,6 +619,7 @@ extrude002 = extrude(profile002, length = 150)
|
||||
test(`View gizmo stays visible even when zoomed out all the way`, async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -632,7 +633,7 @@ extrude002 = extrude(profile002, length = 150)
|
||||
|
||||
await test.step(`Load an empty file`, async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', '')
|
||||
localStorage.setItem('persistCode', '@settings(defaultLengthUnit = in)')
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
@ -646,22 +647,31 @@ extrude002 = extrude(profile002, length = 150)
|
||||
timeout: 5000,
|
||||
message: 'Plane color is visible',
|
||||
})
|
||||
.toBeLessThanOrEqual(15)
|
||||
.toBeLessThanOrEqual(20)
|
||||
await expect(scene.startEditSketchBtn).toBeEnabled()
|
||||
|
||||
let maxZoomOuts = 10
|
||||
let middlePixelIsBackgroundColor =
|
||||
(await middlePixelIsColor(bgColor)) < 10
|
||||
|
||||
console.time('pressing control')
|
||||
await page.keyboard.down('Control')
|
||||
|
||||
while (!middlePixelIsBackgroundColor && maxZoomOuts > 0) {
|
||||
await page.keyboard.down('Control')
|
||||
await page.mouse.move(600, 460)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(600, 50, { steps: 20 })
|
||||
await page.mouse.up({ button: 'right' })
|
||||
await page.keyboard.up('Control')
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.move(650, 460)
|
||||
console.time('moved to start point')
|
||||
await page.mouse.down({ button: 'right' })
|
||||
console.time('moused down')
|
||||
await page.mouse.move(650, 50, { steps: 20 })
|
||||
console.time('moved to end point')
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
console.time('moused up')
|
||||
maxZoomOuts--
|
||||
middlePixelIsBackgroundColor = (await middlePixelIsColor(bgColor)) < 10
|
||||
middlePixelIsBackgroundColor = (await middlePixelIsColor(bgColor)) < 15
|
||||
}
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
expect(middlePixelIsBackgroundColor, {
|
||||
message: 'We should not see the default planes',
|
||||
@ -678,13 +688,12 @@ extrude002 = extrude(profile002, length = 150)
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
viewport,
|
||||
}) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const legoDir = path.join(dir, 'lego')
|
||||
await fsp.mkdir(legoDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('lego.kcl'),
|
||||
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
|
||||
path.join(legoDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
@ -697,11 +706,8 @@ extrude002 = extrude(profile002, length = 150)
|
||||
await scene.loadingIndicator.waitFor({ state: 'detached' })
|
||||
})
|
||||
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
|
||||
await scene.expectPixelColor(
|
||||
[143, 143, 143],
|
||||
{ x: (viewport?.width ?? 1200) / 2, y: (viewport?.height ?? 500) / 2 },
|
||||
15
|
||||
)
|
||||
// TODO: use the viewport size to pick the center point, but the `viewport` fixture's values were wrong.
|
||||
await scene.expectPixelColor([116, 116, 116], { x: 500, y: 250 }, 15)
|
||||
})
|
||||
})
|
||||
|
||||
@ -778,6 +784,19 @@ plane002 = offsetPlane(XZ, offset = -2 * x)`
|
||||
await editor.expectEditor.not.toContain(`plane002`)
|
||||
})
|
||||
})
|
||||
|
||||
test.fail(
|
||||
'Console errors cause tests to fail',
|
||||
async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await homePage.goToModelingScene()
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await page.getByTestId('custom-cmd-input').fill('foobar')
|
||||
await page.getByTestId('custom-cmd-send-button').scrollIntoViewIfNeeded()
|
||||
await page.getByTestId('custom-cmd-send-button').click()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
async function clickExportButton(page: Page) {
|
||||
|
@ -113,7 +113,8 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([2.61, -4.01], %)
|
||||
|> xLine(length = 8.73)
|
||||
|> tangentialArcTo([8.33, -1.31], %)`
|
||||
@ -159,7 +160,10 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
|
||||
.toBe(`sketch002 = startSketchOn(XZ)
|
||||
.toBe(`@settings(defaultLengthUnit = in)
|
||||
|
||||
|
||||
sketch002 = startSketchOn(XZ)
|
||||
sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
|> yLine(length = 12.34)
|
||||
|
||||
@ -789,7 +793,8 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
200
|
||||
)
|
||||
|
||||
let codeStr = 'sketch001 = startSketchOn(XY)'
|
||||
let codeStr =
|
||||
'@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XY)'
|
||||
|
||||
await page.mouse.click(center.x, viewportSize.height * 0.55)
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
@ -868,7 +873,8 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
|
||||
await u.openDebugPanel()
|
||||
|
||||
const code = `sketch001 = startSketchOn(-XZ)
|
||||
const code = `@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(-XZ)
|
||||
profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
|
||||
scale * 34.8
|
||||
)}], sketch001)
|
||||
@ -898,7 +904,7 @@ profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
|
||||
await page.mouse.move(700, 200, { steps: 10 })
|
||||
await page.mouse.click(700, 200, { delay: 200 })
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn(-XZ)`
|
||||
`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(-XZ)`
|
||||
)
|
||||
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
@ -1426,7 +1432,8 @@ test.describe(`Sketching with offset planes`, () => {
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`offsetPlane001 = offsetPlane(XY, offset = 10)`
|
||||
`@settings(defaultLengthUnit = in)
|
||||
offsetPlane001 = offsetPlane(XY, offset = 10)`
|
||||
)
|
||||
})
|
||||
|
||||
@ -1440,7 +1447,7 @@ test.describe(`Sketching with offset planes`, () => {
|
||||
await test.step(`Hovering should highlight code`, async () => {
|
||||
await planeHover()
|
||||
await editor.expectState({
|
||||
activeLines: [`offsetPlane001=offsetPlane(XY,offset=10)`],
|
||||
activeLines: [`@settings(defaultLengthUnit = in)`],
|
||||
diagnostics: [],
|
||||
highlightedCode: 'offsetPlane(XY, offset = 10)',
|
||||
})
|
||||
@ -1453,7 +1460,7 @@ test.describe(`Sketching with offset planes`, () => {
|
||||
await expect(toolbar.lineBtn).toBeEnabled()
|
||||
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
|
||||
await editor.expectState({
|
||||
activeLines: [`offsetPlane001=offsetPlane(XY,offset=10)`],
|
||||
activeLines: [`@settings(defaultLengthUnit = in)`],
|
||||
diagnostics: [],
|
||||
highlightedCode: '',
|
||||
})
|
||||
@ -1604,7 +1611,8 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile002 = startProfileAt([40.68, 87.67], sketch001)
|
||||
|> xLine(length = 239.17)
|
||||
profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
@ -2172,7 +2180,8 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfileAt([6.24, 4.54], sketch001)
|
||||
|> line(end = [-0.41, 6.99])
|
||||
|> line(end = [8.61, 0.74])
|
||||
@ -2317,7 +2326,8 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfileAt([6.24, 4.54], sketch001)
|
||||
|> line(end = [-0.41, 6.99])
|
||||
|> line(end = [8.61, 0.74])
|
||||
@ -2422,7 +2432,8 @@ profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfileAt([-63.43, 193.08], sketch001)
|
||||
|> line(end = [168.52, 149.87])
|
||||
|> line(end = [190.29, -39.18])
|
||||
@ -2486,7 +2497,11 @@ extrude001 = extrude(profile003, length = 5)
|
||||
page,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', `myVar = 5`)
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`@settings(defaultLengthUnit = in)
|
||||
myVar = 5`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
@ -2533,7 +2548,8 @@ extrude001 = extrude(profile003, length = 5)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfileAt([85.19, 338.59], sketch001)
|
||||
|> line(end = [213.3, -94.52])
|
||||
|> line(end = [-230.09, -55.34])
|
||||
@ -2575,7 +2591,8 @@ profile002 = startProfileAt([85.81, 52.55], sketch002)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`thePart = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
thePart = startSketchOn(XZ)
|
||||
|> startProfileAt([7.53, 10.51], %)
|
||||
|> line(end = [12.54, 1.83])
|
||||
|> line(end = [6.65, -6.91])
|
||||
@ -2636,7 +2653,8 @@ extrude001 = extrude(thePart, length = 75)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfileAt([6.71, -3.66], sketch001)
|
||||
|> line(end = [2.65, 9.02], tag = $seg02)
|
||||
|> line(end = [3.73, -9.36], tag = $seg01)
|
||||
@ -2809,7 +2827,8 @@ extrude003 = extrude(profile011, length = 2.5)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfileAt([34, 42.66], sketch001)
|
||||
|> line(end = [102.65, 151.99])
|
||||
|> line(end = [76, -138.66])
|
||||
|
@ -76,11 +76,11 @@ part001 = startSketchOn(-XZ)
|
||||
|> xLine(endAbsolute = totalLen, tag = $seg03)
|
||||
|> yLine(length = -armThick, tag = $seg01)
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
angle = turns::HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg04
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg04, %) + 180, ZERO], %)
|
||||
|> angledLineToY([segAng(seg04, %) + 180, turns::ZERO], %)
|
||||
|> angledLineToY({
|
||||
angle = -bottomAng,
|
||||
to = -totalHeightHalf - armThick,
|
||||
@ -88,12 +88,12 @@ part001 = startSketchOn(-XZ)
|
||||
|> xLine(length = endAbsolute = segEndX(seg03) + 0)
|
||||
|> yLine(length = -segLen(seg01, %))
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
angle = turns::HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg02
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg02, %) + 180, -baseHeight], %)
|
||||
|> xLine(endAbsolute = ZERO)
|
||||
|> xLine(endAbsolute = turns::ZERO)
|
||||
|> close()
|
||||
|> extrude(length = 4)`
|
||||
)
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 67 KiB |
@ -1,5 +1,5 @@
|
||||
{
|
||||
"original_source_code": "sketch001 = startSketchOn('XZ')\nprofile001 = startProfileAt([57.81, 250.51], sketch001)\n |> line(end = [121.13, 56.63], tag = $seg02)\n |> line(end = [83.37, -34.61], tag = $seg01)\n |> line(end = [19.66, -116.4])\n |> line(end = [-221.8, -41.69])\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude001 = extrude(profile001, length = 200)\nsketch002 = startSketchOn('XZ')\n |> startProfileAt([-73.64, -42.89], %)\n |> xLine(length = 173.71)\n |> line(end = [-22.12, -94.4])\n |> xLine(length = -156.98)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude002 = extrude(sketch002, length = 50)\nsketch003 = startSketchOn('XY')\n |> startProfileAt([52.92, 157.81], %)\n |> angledLine([0, 176.4], %, $rectangleSegmentA001)\n |> angledLine([\n segAng(rectangleSegmentA001) - 90,\n 53.4\n ], %, $rectangleSegmentB001)\n |> angledLine([\n segAng(rectangleSegmentA001),\n -segLen(rectangleSegmentA001)\n ], %, $rectangleSegmentC001)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude003 = extrude(sketch003, length = 20)\n",
|
||||
"original_source_code": "sketch001 = startSketchOn('XZ')\nprofile001 = startProfileAt([57.81, 250.51], sketch001)\n |> line(end = [121.13, 56.63], tag = $seg02)\n |> line(end = [83.37, -34.61], tag = $seg01)\n |> line(end = [19.66, -116.4])\n |> line(end = [-221.8, -41.69])\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude001 = extrude(profile001, length = 200)\nsketch002 = startSketchOn('XZ')\n |> startProfileAt([-73.64, -42.89], %)\n |> xLine(length = 173.71)\n |> line(end = [-22.12, -94.4])\n |> xLine(length = -156.98)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude002 = extrude(sketch002, length = 50)\nsketch003 = startSketchOn(XY)\n |> startProfileAt([52.92, 157.81], %)\n |> angledLine([0, 176.4], %, $rectangleSegmentA001)\n |> angledLine([\n segAng(rectangleSegmentA001) - 90,\n 53.4\n ], %, $rectangleSegmentB001)\n |> angledLine([\n segAng(rectangleSegmentA001),\n -segLen(rectangleSegmentA001)\n ], %, $rectangleSegmentC001)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude003 = extrude(sketch003, length = 20)\n",
|
||||
"prompt": "make this neon green please, use #39FF14",
|
||||
"source_ranges": [
|
||||
{
|
||||
|
@ -29,5 +29,5 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"kcl_version": "0.2.53"
|
||||
"kcl_version": "0.2.54"
|
||||
}
|
@ -69,28 +69,31 @@ describe('utility to bypass unreliable tests', () => {
|
||||
afterAll(() => {
|
||||
process.env = { ...originalEnv }
|
||||
})
|
||||
|
||||
it('always runs them on dedicated branch', () => {
|
||||
process.env.GITHUB_EVENT_NAME = 'push'
|
||||
process.env.GITHUB_REF = 'refs/heads/all-e2e'
|
||||
process.env.GITHUB_HEAD_REF = ''
|
||||
process.env.GITHUB_BASE_REF = ''
|
||||
const condition = orRunWhenFullSuiteEnabled()
|
||||
expect(condition).toBe(false)
|
||||
const shouldSkip = orRunWhenFullSuiteEnabled()
|
||||
expect(shouldSkip).toBe(false)
|
||||
})
|
||||
|
||||
it('skips them on the main branch', () => {
|
||||
process.env.GITHUB_EVENT_NAME = 'push'
|
||||
process.env.GITHUB_REF = 'refs/heads/main'
|
||||
process.env.GITHUB_HEAD_REF = ''
|
||||
process.env.GITHUB_BASE_REF = ''
|
||||
const condition = orRunWhenFullSuiteEnabled()
|
||||
expect(condition).toBe(true)
|
||||
const shouldSkip = orRunWhenFullSuiteEnabled()
|
||||
expect(shouldSkip).toBe(true)
|
||||
})
|
||||
|
||||
it('skips them on pull requests', () => {
|
||||
process.env.GITHUB_EVENT_NAME = 'pull_request'
|
||||
process.env.GITHUB_REF = 'refs/pull/5883/merge'
|
||||
process.env.GITHUB_HEAD_REF = 'my-branch'
|
||||
process.env.GITHUB_BASE_REF = 'main'
|
||||
const condition = orRunWhenFullSuiteEnabled()
|
||||
expect(condition).toBe(true)
|
||||
const shouldSkip = orRunWhenFullSuiteEnabled()
|
||||
expect(shouldSkip).toBe(true)
|
||||
})
|
||||
})
|
||||
|
@ -26,6 +26,7 @@ import { isArray } from 'lib/utils'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
import { Configuration } from 'lang/wasm'
|
||||
import { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
|
||||
|
||||
const toNormalizedCode = (text: string) => {
|
||||
return text.replace(/\s+/g, '')
|
||||
@ -761,7 +762,7 @@ export interface Paths {
|
||||
}
|
||||
|
||||
export const doExport = async (
|
||||
output: Models['OutputFormat_type'],
|
||||
output: Models['OutputFormat3d_type'],
|
||||
rootDir: string,
|
||||
page: Page,
|
||||
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
||||
@ -934,47 +935,39 @@ export async function setup(
|
||||
}
|
||||
|
||||
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
||||
// enabled for chrome for now
|
||||
if (page.context().browser()?.browserType().name() === 'chromium') {
|
||||
// No idea wtf exception is
|
||||
page.on('pageerror', (exception: any) => {
|
||||
if (isErrorWhitelisted(exception)) {
|
||||
return
|
||||
}
|
||||
page.on('pageerror', (exception: any) => {
|
||||
if (isErrorWhitelisted(exception)) {
|
||||
return
|
||||
}
|
||||
// Only disable this environment variable if you want to collect console errors
|
||||
if (process.env.FAIL_ON_CONSOLE_ERRORS !== 'false') {
|
||||
// Use expect to prevent page from closing and not cleaning up
|
||||
expect(`An error was detected in the console: \r\n message:${exception.message} \r\n name:${exception.name} \r\n stack:${exception.stack}
|
||||
|
||||
// only set this env var to false if you want to collect console errors
|
||||
// This can be configured in the GH workflow. This should be set to true by default (we want tests to fail when
|
||||
// unwhitelisted console errors are detected).
|
||||
if (process.env.FAIL_ON_CONSOLE_ERRORS === 'true') {
|
||||
// Fail when running on CI and FAIL_ON_CONSOLE_ERRORS is set
|
||||
// use expect to prevent page from closing and not cleaning up
|
||||
expect(`An error was detected in the console: \r\n message:${exception.message} \r\n name:${exception.name} \r\n stack:${exception.stack}
|
||||
|
||||
*Either fix the console error or add it to the whitelist defined in ./lib/console-error-whitelist.ts (if the error can be safely ignored)
|
||||
`).toEqual('Console error detected')
|
||||
} else {
|
||||
// the (test-results/exceptions.txt) file will be uploaded as part of an upload artifact in GH
|
||||
fsp
|
||||
.appendFile(
|
||||
'./test-results/exceptions.txt',
|
||||
[
|
||||
'~~~',
|
||||
`triggered_by_test:${
|
||||
testInfo?.file + ' ' + (testInfo?.title || ' ')
|
||||
}`,
|
||||
`name:${exception.name}`,
|
||||
`message:${exception.message}`,
|
||||
`stack:${exception.stack}`,
|
||||
`project:${testInfo?.project.name}`,
|
||||
'~~~',
|
||||
].join('\n')
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
*Either fix the console error or add it to the whitelist defined in ./lib/console-error-whitelist.ts (if the error can be safely ignored)
|
||||
`).toEqual('Console error detected')
|
||||
} else {
|
||||
// Add errors to `test-results/exceptions.txt` as a test artifact
|
||||
fsp
|
||||
.appendFile(
|
||||
'./test-results/exceptions.txt',
|
||||
[
|
||||
'~~~',
|
||||
`triggered_by_test:${
|
||||
testInfo?.file + ' ' + (testInfo?.title || ' ')
|
||||
}`,
|
||||
`name:${exception.name}`,
|
||||
`message:${exception.message}`,
|
||||
`stack:${exception.stack}`,
|
||||
`project:${testInfo?.project.name}`,
|
||||
'~~~',
|
||||
].join('\n')
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
export async function isOutOfViewInScrollContainer(
|
||||
element: Locator,
|
||||
@ -1125,3 +1118,15 @@ export function settingsToToml(settings: DeepPartial<Configuration>) {
|
||||
export function tomlToSettings(toml: string): DeepPartial<Configuration> {
|
||||
return TOML.parse(toml)
|
||||
}
|
||||
|
||||
export function tomlToPerProjectSettings(
|
||||
toml: string
|
||||
): DeepPartial<ProjectConfiguration> {
|
||||
return TOML.parse(toml)
|
||||
}
|
||||
|
||||
export function perProjectsettingsToToml(
|
||||
settings: DeepPartial<ProjectConfiguration>
|
||||
) {
|
||||
return TOML.stringify(settings as any)
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import {
|
||||
getUtils,
|
||||
TEST_COLORS,
|
||||
pollEditorLinesSelectedLength,
|
||||
executorInputPath,
|
||||
orRunWhenFullSuiteEnabled,
|
||||
} from './test-utils'
|
||||
import { XOR } from 'lib/utils'
|
||||
@ -81,7 +80,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 79
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 79
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4], tag = $seg01)
|
||||
@ -145,7 +145,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 5
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4], tag = $seg01)
|
||||
@ -159,31 +160,6 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
|> xLine(length = segLen(seg_what))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])`
|
||||
)
|
||||
|
||||
const isChecked = await createNewVariableCheckbox.isChecked()
|
||||
const addVariable = testName === 'Add variable'
|
||||
XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct
|
||||
(await createNewVariableCheckbox.click())
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'Add constraining value' })
|
||||
.click()
|
||||
|
||||
// Wait for the codemod to take effect
|
||||
await expect(page.locator('.cm-content')).toContainText(`angle: -57,`)
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
`offset: ${offset},`
|
||||
)
|
||||
|
||||
await pollEditorLinesSelectedLength(page, 2)
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent[0]).toHaveText(
|
||||
`|> line(end = [74.36, 130.4], tag = $seg01)`
|
||||
)
|
||||
await expect(activeLinesContent[1]).toHaveText(`}, %)`)
|
||||
|
||||
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
@ -277,7 +253,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 5
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4])
|
||||
@ -387,7 +364,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 5
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4])
|
||||
@ -486,13 +464,13 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
testName: 'Add variable, selecting axis',
|
||||
addVariable: true,
|
||||
axisSelect: true,
|
||||
value: 'QUARTER_TURN - angle001',
|
||||
value: 'turns::QUARTER_TURN - angle001',
|
||||
},
|
||||
{
|
||||
testName: 'No variable, selecting axis',
|
||||
addVariable: false,
|
||||
axisSelect: true,
|
||||
value: 'QUARTER_TURN - 7',
|
||||
value: 'turns::QUARTER_TURN - 7',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, axisSelect } of cases) {
|
||||
@ -500,7 +478,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 5
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4])
|
||||
@ -602,7 +581,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 5
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4])
|
||||
@ -688,7 +668,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 5
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4])
|
||||
@ -768,7 +749,8 @@ part002 = startSketchOn(XZ)
|
||||
await page.addInitScript(async (customCode) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 5
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4])
|
||||
@ -869,7 +851,8 @@ part002 = startSketchOn(XZ)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 5
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4])
|
||||
@ -935,12 +918,12 @@ part002 = startSketchOn(XZ)
|
||||
test.describe('Axis & segment - no modal constraints', () => {
|
||||
const cases = [
|
||||
{
|
||||
codeAfter: `|> line(endAbsolute = [154.9, ZERO])`,
|
||||
codeAfter: `|> line(endAbsolute = [154.9, turns::ZERO])`,
|
||||
axisClick: { x: 950, y: 250 },
|
||||
constraintName: 'Snap To X',
|
||||
},
|
||||
{
|
||||
codeAfter: `|> line(endAbsolute = [ZERO, 61.34])`,
|
||||
codeAfter: `|> line(endAbsolute = [turns::ZERO, 61.34])`,
|
||||
axisClick: { x: 600, y: 150 },
|
||||
constraintName: 'Snap To Y',
|
||||
},
|
||||
@ -950,7 +933,8 @@ part002 = startSketchOn(XZ)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 5
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line(end = [74.36, 130.4])
|
||||
@ -1117,9 +1101,19 @@ test.describe('Electron constraint tests', () => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'test-sample')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('angled_line.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
await fsp.writeFile(
|
||||
path.join(bracketDir, 'main.kcl'),
|
||||
`@settings(defaultLengthUnit = in)
|
||||
const part001 = startSketchOn(XY)
|
||||
|> startProfileAt([4.83, 12.56], %)
|
||||
|> line(end = [15.1, 2.48])
|
||||
|> line(end = [3.15, -9.85], tag = $seg01)
|
||||
|> line(end = [-15.17, -4.1])
|
||||
|> angledLine([segAng(seg01), 12.35], %)
|
||||
|> line(end = [-13.02, 10.03])
|
||||
|> close()
|
||||
|> extrude(length = 4)`,
|
||||
'utf-8'
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -255,7 +255,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`
|
||||
`@settings(defaultLengthUnit = in)
|
||||
const sketch002 = startSketchOn(XZ)
|
||||
|> startProfileAt([-108.83, -57.48], %)
|
||||
|> angledLine([0, 105.13], %, $rectangleSegmentA001)
|
||||
|
@ -46,7 +46,7 @@ test.describe('Testing in-app sample loading', () => {
|
||||
page.getByRole('option', {
|
||||
name,
|
||||
})
|
||||
const warningText = page.getByText('Overwrite current file and units?')
|
||||
const warningText = page.getByText('Overwrite current file with sample?')
|
||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
||||
|
||||
await test.step(`Precondition: check the initial code`, async () => {
|
||||
@ -110,11 +110,9 @@ test.describe('Testing in-app sample loading', () => {
|
||||
const commandMethodOption = page.getByRole('option', {
|
||||
name: 'Overwrite',
|
||||
})
|
||||
const newFileWarning = page.getByText(
|
||||
'Create a new file, overwrite project units?'
|
||||
)
|
||||
const newFileWarning = page.getByText('Create a new file from sample?')
|
||||
const overwriteWarning = page.getByText(
|
||||
'Overwrite current file and units?'
|
||||
'Overwrite current file with sample?'
|
||||
)
|
||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
|
@ -210,7 +210,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([5 + 0, 20 + 0], %)
|
||||
|> line(end = [0.5, -14 + 0])
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
@ -380,7 +381,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yRel001 = -14
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yRel001 = -14
|
||||
xRel001 = 0.5
|
||||
angle001 = 3
|
||||
len001 = 32
|
||||
@ -459,7 +461,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [0.5, -14 + 0])
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
@ -590,7 +593,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [0.5, -14 + 0])
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
@ -751,7 +755,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [0.5, -14 + 0])
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
@ -831,7 +836,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfileAt([56.37, 120.33], sketch001)
|
||||
|> line(end = [162.86, 106.48])
|
||||
|> arcTo({
|
||||
@ -957,7 +963,8 @@ profile001 = startProfileAt([56.37, 120.33], sketch001)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
part001 = startSketchOn(XZ)
|
||||
|> circle(center = [1 + 0, 0], radius = 8)
|
||||
`
|
||||
)
|
||||
@ -1077,7 +1084,8 @@ profile001 = startProfileAt([56.37, 120.33], sketch001)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
part001 = startSketchOn(XZ)
|
||||
|>startProfileAt([0, 0], %)
|
||||
|> line(end = [0.5, -14 + 0])
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
@ -1351,7 +1359,8 @@ profile001 = startProfileAt([56.37, 120.33], sketch001)
|
||||
async ({ lineToBeDeleted, extraLine }) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([5, 6], %)
|
||||
|> ${lineToBeDeleted}
|
||||
|> line(end = [-10, -15])
|
||||
@ -1516,7 +1525,8 @@ profile001 = startProfileAt([56.37, 120.33], sketch001)
|
||||
async ({ lineToBeDeleted }) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([5, 6], %)
|
||||
|> ${lineToBeDeleted}
|
||||
|> line(end = [-10, -15])
|
||||
|
@ -68,20 +68,20 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||
`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||
)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||
|> xLine(length = ${commonPoints.num1})`)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
|
||||
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(length = ${commonPoints.num1})
|
||||
@ -89,7 +89,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
|
||||
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(length = ${commonPoints.num1})
|
||||
@ -260,7 +260,8 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line(end = [112.54, 127.64], tag = $seg02)
|
||||
|> line(end = [170.36, -121.61], tag = $seg01)
|
||||
@ -528,7 +529,8 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
|
||||
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([20, 0], %)
|
||||
|> line(end = [7.13, 4 + 0])
|
||||
|> angledLine({ angle = 3 + 0, length = 3.14 + 0 }, %)
|
||||
@ -747,7 +749,8 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await u.removeCurrentCode()
|
||||
await u.codeLocator.fill(`sketch001 = startSketchOn(XZ)
|
||||
await u.codeLocator.fill(`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
@ -965,7 +968,8 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
|
||||
async ({ cases }) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 79
|
||||
`@settings(defaultLengthUnit = in)
|
||||
yo = 79
|
||||
part001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> ${cases[0].expectedCode}
|
||||
@ -1020,7 +1024,8 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line(end = [112.54, 127.64])
|
||||
|> line(end = [170.36, -121.61], tag = $seg01)
|
||||
@ -1253,7 +1258,7 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn(XZ)`
|
||||
`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)`
|
||||
)
|
||||
|
||||
await page.waitForTimeout(600)
|
||||
|
@ -271,7 +271,7 @@ test.describe('Testing settings', () => {
|
||||
const bracketDir = join(dir, projectName)
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
}
|
||||
@ -699,19 +699,19 @@ test.describe('Testing settings', () => {
|
||||
name: 'Current units are: ',
|
||||
})
|
||||
await gizmo.click()
|
||||
const button = page.getByRole('button', {
|
||||
const button = page.locator('ul').getByRole('button', {
|
||||
name: copy,
|
||||
exact: true,
|
||||
})
|
||||
await button.click()
|
||||
const toastMessage = page.getByText(
|
||||
`Set default unit to "${unitOfMeasure}" for this project`
|
||||
`Updated per-file units to ${unitOfMeasure}`
|
||||
)
|
||||
await expect(toastMessage).toBeVisible()
|
||||
}
|
||||
|
||||
await changeUnitOfMeasureInGizmo('in', 'Inches')
|
||||
await changeUnitOfMeasureInGizmo('ft', 'Feet')
|
||||
await changeUnitOfMeasureInGizmo('in', 'Inches')
|
||||
await changeUnitOfMeasureInGizmo('yd', 'Yards')
|
||||
await changeUnitOfMeasureInGizmo('mm', 'Millimeters')
|
||||
await changeUnitOfMeasureInGizmo('cm', 'Centimeters')
|
||||
@ -951,9 +951,9 @@ test.describe('Testing settings', () => {
|
||||
)
|
||||
})
|
||||
|
||||
await test.step(`Initial units from settings`, async () => {
|
||||
await test.step(`Initial units from settings are ignored`, async () => {
|
||||
await homePage.openProject('project-000')
|
||||
await expect(unitsIndicator).toHaveText('Current units are: in')
|
||||
await expect(unitsIndicator).toHaveText('Current units are: mm')
|
||||
})
|
||||
|
||||
await test.step(`Manually write inline settings`, async () => {
|
||||
|
@ -67,11 +67,11 @@ part001 = startSketchOn(-XZ)
|
||||
|> xLine(endAbsolute = totalLen, tag = $seg03)
|
||||
|> yLine(length = -armThick, tag = $seg01)
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
angle = turns::HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg04
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg04) + 180, ZERO], %)
|
||||
|> angledLineToY([segAng(seg04) + 180, turns::ZERO], %)
|
||||
|> angledLineToY({
|
||||
angle = -bottomAng,
|
||||
to = -totalHeightHalf - armThick,
|
||||
@ -79,12 +79,12 @@ part001 = startSketchOn(-XZ)
|
||||
|> xLine(endAbsolute = segEndX(seg03) + 0)
|
||||
|> yLine(length = -segLen(seg01))
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
angle = turns::HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg02
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
|
||||
|> xLine(endAbsolute = ZERO)
|
||||
|> xLine(endAbsolute = turns::ZERO)
|
||||
|> close()
|
||||
|> extrude(length = 4)`
|
||||
)
|
||||
@ -483,7 +483,8 @@ test('Sketch on face', async ({ page, homePage, scene, cmdBar, toolbar }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
`@settings(defaultLengthUnit = in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line(end = [2.48, 2.44])
|
||||
|> line(end = [2.66, 1.17])
|
||||
|
20
interface.d.ts
vendored
@ -3,6 +3,18 @@ import fsSync from 'node:fs'
|
||||
import path from 'path'
|
||||
import { dialog, shell } from 'electron'
|
||||
import { MachinesListing } from 'components/MachineManagerProvider'
|
||||
import type { Channel } from 'src/menu/channels'
|
||||
import { Menu, WebContents } from 'electron'
|
||||
import { ZooLabel, ZooMenuEvents } from 'menu/roles'
|
||||
import type { MenuActionIPC } from 'menu/rules'
|
||||
import type { WebContentSendPayload } from 'menu/channels'
|
||||
|
||||
// Extend the interface with additional custom properties
|
||||
declare module 'electron' {
|
||||
interface Menu {
|
||||
label?: ZooLabel
|
||||
}
|
||||
}
|
||||
|
||||
type EnvFn = (value?: string) => string
|
||||
|
||||
@ -94,6 +106,14 @@ export interface IElectronAPI {
|
||||
appCheckForUpdates: () => Promise<unknown>
|
||||
getArgvParsed: () => any
|
||||
getAppTestProperty: (propertyName: string) => any
|
||||
|
||||
// Helper functions to create application Menus
|
||||
createHomePageMenu: () => Promise<any>
|
||||
createModelingPageMenu: () => Promise<any>
|
||||
createFallbackMenu: () => Promise<any>
|
||||
enableMenu(menuId: string): Promise<any>
|
||||
disableMenu(menuId: string): Promise<any>
|
||||
menuOn: (callback: (payload: WebContentSendPayload) => void) => any
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
36
package.json
@ -13,20 +13,20 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/commands": "^6.8.0",
|
||||
"@codemirror/commands": "^6.8.1",
|
||||
"@codemirror/language": "^6.11.0",
|
||||
"@codemirror/lint": "^6.8.4",
|
||||
"@codemirror/lint": "^6.8.5",
|
||||
"@codemirror/search": "^6.5.10",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@csstools/postcss-oklab-function": "^4.0.7",
|
||||
"@csstools/postcss-oklab-function": "^4.0.8",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.19",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@kittycad/lib": "2.0.17",
|
||||
"@headlessui/tailwindcss": "^0.2.2",
|
||||
"@kittycad/lib": "2.0.23",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@lezer/lr": "^1.4.1",
|
||||
"@react-hook/resize-observer": "^2.0.1",
|
||||
@ -37,11 +37,11 @@
|
||||
"@xstate/react": "^4.1.1",
|
||||
"bonjour-service": "^1.3.0",
|
||||
"bson": "^6.10.3",
|
||||
"chokidar": "^4.0.1",
|
||||
"chokidar": "^4.0.3",
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"diff": "^7.0.0",
|
||||
"electron-updater": "^6.6.0",
|
||||
"electron-updater": "^6.6.2",
|
||||
"fuse.js": "^7.1.0",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
@ -94,9 +94,11 @@
|
||||
"fetch:wasm": "./scripts/get-latest-wasm-bundle.sh",
|
||||
"fetch:wasm:windows": "./scripts/get-latest-wasm-bundle.ps1",
|
||||
"fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples",
|
||||
"build:wasm-dev": "yarn wasm-prep && (cd rust && wasm-pack build kcl-wasm-lib --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt:generated",
|
||||
"build:wasm": "./scripts/build-wasm.sh",
|
||||
"build:wasm:windows": "./scripts/build-wasm.ps1",
|
||||
"build:wasm-dev": "yarn build:wasm:dev",
|
||||
"build:wasm:dev": "./scripts/build-wasm-dev.sh",
|
||||
"build:wasm:dev:windows": "./scripts/build-wasm-dev.ps1",
|
||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",
|
||||
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",
|
||||
@ -162,15 +164,15 @@
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.2",
|
||||
"@nabla/vite-plugin-eslint": "^2.0.5",
|
||||
"@playwright/test": "^1.51.0",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
"@types/diff": "^7.0.1",
|
||||
"@types/diff": "^7.0.2",
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/isomorphic-fetch": "^0.0.39",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^22.13.9",
|
||||
"@types/node": "^22.13.14",
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.3.4",
|
||||
@ -180,13 +182,13 @@
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
"@types/ws": "^8.5.13",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"@types/ws": "^8.18.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@vitest/web-worker": "^1.5.0",
|
||||
"@xstate/cli": "^0.5.17",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"electron": "^34.1.1",
|
||||
"electron-builder": "^26.0.6",
|
||||
"electron-builder": "^26.0.12",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
@ -218,7 +220,7 @@
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.1",
|
||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||
"ws": "^8.17.0",
|
||||
"ws": "^8.18.1",
|
||||
"yarn": "^1.22.22"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
|
@ -90,7 +90,7 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
|
||||
}
|
||||
|
||||
add(bytes: Uint8Array): void {
|
||||
const message = Codec.decode(bytes) as vsrpc.Message
|
||||
const message = Codec.decode<vsrpc.Message>(bytes)
|
||||
if (this.trace) {
|
||||
Tracer.server(message)
|
||||
}
|
||||
|
@ -37,11 +37,11 @@ export class IntoServer
|
||||
implements AsyncGenerator<Uint8Array, never, void>
|
||||
{
|
||||
private worker: Worker | null = null
|
||||
private type_: String | null = null
|
||||
private type_: string | null = null
|
||||
|
||||
private trace: boolean = false
|
||||
|
||||
constructor(type_?: String, worker?: Worker, trace?: boolean) {
|
||||
constructor(type_?: string, worker?: Worker, trace?: boolean) {
|
||||
super()
|
||||
if (worker && type_) {
|
||||
this.worker = worker
|
||||
|
@ -7,6 +7,7 @@ import { platform } from 'os'
|
||||
export default defineConfig({
|
||||
timeout: 120_000, // override the default 30s timeout
|
||||
testDir: './e2e/playwright',
|
||||
testIgnore: '*.test.ts', // ignore unit tests
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
|
@ -7,7 +7,7 @@
|
||||
// Define function
|
||||
fn rail8020(originStart, railHeight, railLength) {
|
||||
// Sketch side 1 of profile
|
||||
sketch001 = startSketchOn('-XZ')
|
||||
sketch001 = startSketchOn(-XZ)
|
||||
|> startProfileAt([
|
||||
originStart[0],
|
||||
0.1 * railHeight + originStart[1]
|
||||
@ -194,7 +194,7 @@ fn rail8020(originStart, railHeight, railLength) {
|
||||
.5 * railHeight + originStart[0],
|
||||
.5 * railHeight + originStart[1]
|
||||
],
|
||||
radius = .205 * railHeight / 2
|
||||
radius = .205 * railHeight / 2,
|
||||
), %)
|
||||
|> extrude(length = railLength)
|
||||
|> fillet(
|
||||
@ -216,7 +216,7 @@ fn rail8020(originStart, railHeight, railLength) {
|
||||
getNextAdjacentEdge(edge28),
|
||||
getNextAdjacentEdge(edge29),
|
||||
getNextAdjacentEdge(edge30)
|
||||
]
|
||||
],
|
||||
)
|
||||
|> fillet(
|
||||
radius = 0.03,
|
||||
@ -237,7 +237,7 @@ fn rail8020(originStart, railHeight, railLength) {
|
||||
getNextAdjacentEdge(edge26),
|
||||
getNextAdjacentEdge(edge31),
|
||||
getNextAdjacentEdge(edge32)
|
||||
]
|
||||
],
|
||||
)
|
||||
return sketch001
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ When you submit a PR to add or modify KCL samples, images and STEP files will be
|
||||
[](cycloidal-gear/main.kcl)
|
||||
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
|
||||
[](dodecahedron/main.kcl)
|
||||
#### [dual-basin-utility-sink](dual-basin-utility-sink/main.kcl) ([screenshot](screenshots/dual-basin-utility-sink.png))
|
||||
[](dual-basin-utility-sink/main.kcl)
|
||||
#### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png))
|
||||
[](enclosure/main.kcl)
|
||||
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([screenshot](screenshots/exhaust-manifold.png))
|
||||
@ -75,6 +77,8 @@ When you submit a PR to add or modify KCL samples, images and STEP files will be
|
||||
[](kitt/main.kcl)
|
||||
#### [lego](lego/main.kcl) ([screenshot](screenshots/lego.png))
|
||||
[](lego/main.kcl)
|
||||
#### [makeup-mirror](makeup-mirror/main.kcl) ([screenshot](screenshots/makeup-mirror.png))
|
||||
[](makeup-mirror/main.kcl)
|
||||
#### [mounting-plate](mounting-plate/main.kcl) ([screenshot](screenshots/mounting-plate.png))
|
||||
[](mounting-plate/main.kcl)
|
||||
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
|
||||
|
@ -15,57 +15,37 @@ padding = 1.5
|
||||
bearingDia = 3
|
||||
|
||||
// (Needs to be updated). Sketch the block and extrude up to where the counterbore diameter starts.
|
||||
extrude001 = startSketchOn('XY')
|
||||
extrude001 = startSketchOn(XY)
|
||||
|> startProfileAt([-width / 2, -length / 2], %)
|
||||
|> line(endAbsolute = [width / 2, -length / 2])
|
||||
|> line(endAbsolute = [width / 2, length / 2])
|
||||
|> line(endAbsolute = [-width / 2, length / 2])
|
||||
|> close()
|
||||
|> extrude(length = height)
|
||||
|
||||
extrude002 = startSketchOn(extrude001, 'end')
|
||||
|> circle(
|
||||
center = [
|
||||
center = [
|
||||
-(width / 2 - (padding / 2)),
|
||||
-(length / 2 - (padding / 2))
|
||||
],
|
||||
radius = cbDia / 2,
|
||||
],
|
||||
radius = cbDia / 2,
|
||||
)
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = length - padding,
|
||||
axis = [0, 1],
|
||||
)
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = width - padding,
|
||||
axis = [1, 0],
|
||||
)
|
||||
|> patternLinear2d(instances = 2, distance = length - padding, axis = [0, 1])
|
||||
|> patternLinear2d(instances = 2, distance = width - padding, axis = [1, 0])
|
||||
|> extrude(%, length = -cbDepth)
|
||||
|
||||
extrude003 = startSketchOn(extrude001, 'start')
|
||||
|> circle(
|
||||
center = [
|
||||
-(width / 2 - (padding / 2)),
|
||||
-(length / 2 - (padding / 2))
|
||||
],
|
||||
radius = holeDia / 2,
|
||||
center = [
|
||||
-(width / 2 - (padding / 2)),
|
||||
-(length / 2 - (padding / 2))
|
||||
],
|
||||
radius = holeDia / 2,
|
||||
)
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = length - padding,
|
||||
axis = [0, 1],
|
||||
)
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = width - padding,
|
||||
axis = [1, 0],
|
||||
)
|
||||
|> patternLinear2d(instances = 2, distance = length - padding, axis = [0, 1])
|
||||
|> patternLinear2d(instances = 2, distance = width - padding, axis = [1, 0])
|
||||
|> extrude(length = -height + cbDepth)
|
||||
|
||||
extrude004 = startSketchOn(extrude001, 'end')
|
||||
|> circle(
|
||||
center = [0, 0],
|
||||
radius = bearingDia/2,
|
||||
)
|
||||
|> circle(center = [0, 0], radius = bearingDia / 2)
|
||||
|> extrude(length = -height)
|
@ -17,21 +17,15 @@ chainThickness = sphereDia / 8
|
||||
linkDiameter = sphereDia / 4
|
||||
|
||||
// Sketch the inside bearing piece
|
||||
insideWallSketch = startSketchOn(offsetPlane("XY", offset = -overallThickness / 2))
|
||||
|> circle(
|
||||
center = [0, 0],
|
||||
radius = shaftDia / 2 + wallThickness
|
||||
)
|
||||
|> hole(circle(
|
||||
center = [0, 0],
|
||||
radius = shaftDia / 2
|
||||
), %)
|
||||
insideWallSketch = startSketchOn(offsetPlane(XY, offset = -overallThickness / 2))
|
||||
|> circle(center = [0, 0], radius = shaftDia / 2 + wallThickness)
|
||||
|> hole(circle(center = [0, 0], radius = shaftDia / 2), %)
|
||||
|
||||
// Extrude the inside bearing piece
|
||||
insideWall = extrude(insideWallSketch, length = overallThickness)
|
||||
|
||||
// Create the sketch of one of the balls
|
||||
ballsSketch = startSketchOn("XY")
|
||||
ballsSketch = startSketchOn(XY)
|
||||
|> startProfileAt([shaftDia / 2 + wallThickness, 0.001], %)
|
||||
|> arc({
|
||||
angleEnd = 0,
|
||||
@ -47,11 +41,11 @@ balls = revolve(ballsSketch, axis = "X")
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
instances = nBalls,
|
||||
rotateDuplicates = true
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
// Create the sketch for the chain around the balls
|
||||
chainSketch = startSketchOn("XY")
|
||||
chainSketch = startSketchOn(XY)
|
||||
|> startProfileAt([
|
||||
shaftDia / 2 + wallThickness + sphereDia / 2 - (chainWidth / 2),
|
||||
0.125 * sin(toRadians(60))
|
||||
@ -72,17 +66,17 @@ chainHead = revolve(chainSketch, axis = "X")
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
instances = nBalls,
|
||||
rotateDuplicates = true
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
// Create the sketch for the links in between the chains
|
||||
linkSketch = startSketchOn("XZ")
|
||||
linkSketch = startSketchOn(XZ)
|
||||
|> circle(
|
||||
center = [
|
||||
shaftDia / 2 + wallThickness + sphereDia / 2,
|
||||
0
|
||||
],
|
||||
radius = linkDiameter / 2
|
||||
radius = linkDiameter / 2,
|
||||
)
|
||||
|
||||
// Revolve the link sketch
|
||||
@ -92,19 +86,13 @@ linkRevolve = revolve(linkSketch, axis = 'Y', angle = 360 / nBalls)
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
instances = nBalls,
|
||||
rotateDuplicates = true
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
// Create the sketch for the outside walls
|
||||
outsideWallSketch = startSketchOn(offsetPlane("XY", offset = -overallThickness / 2))
|
||||
|> circle(
|
||||
center = [0, 0],
|
||||
radius = outsideDiameter / 2
|
||||
)
|
||||
|> hole(circle(
|
||||
center = [0, 0],
|
||||
radius = shaftDia / 2 + wallThickness + sphereDia
|
||||
), %)
|
||||
outsideWallSketch = startSketchOn(offsetPlane(XY, offset = -overallThickness / 2))
|
||||
|> circle(center = [0, 0], radius = outsideDiameter / 2)
|
||||
|> hole(circle(center = [0, 0], radius = shaftDia / 2 + wallThickness + sphereDia), %)
|
||||
|
||||
outsideWall = extrude(outsideWallSketch, length = overallThickness)
|
||||
|
||||
|
@ -130,7 +130,7 @@ fn armRestProfile(plane, offset) {
|
||||
|
||||
export fn armRest(plane, offset) {
|
||||
path = armRestPath( offsetPlane(plane, offset = offset))
|
||||
profile = armRestProfile( offsetPlane("-XZ", offset = 20), offset)
|
||||
profile = armRestProfile( offsetPlane(-XZ, offset = 20), offset)
|
||||
sweep(profile, path = path)
|
||||
return 0
|
||||
}
|
||||
|
@ -16,19 +16,19 @@ import backSlats from "bench-parts.kcl"
|
||||
import armRest from "bench-parts.kcl"
|
||||
|
||||
// Create the dividers, these hold the seat and back slats
|
||||
divider("YZ")
|
||||
divider(offsetPlane("-YZ", offset = benchLength / 2))
|
||||
divider(offsetPlane("YZ", offset = benchLength / 2))
|
||||
divider(YZ)
|
||||
divider(offsetPlane(-YZ, offset = benchLength / 2))
|
||||
divider(offsetPlane(YZ, offset = benchLength / 2))
|
||||
|
||||
// Create the connectors to join the dividers
|
||||
connector(offsetPlane("YZ", offset = -benchLength / 2), benchLength)
|
||||
connector(offsetPlane(YZ, offset = -benchLength / 2), benchLength)
|
||||
|
||||
// Create the seat slats
|
||||
seatSlats(offsetPlane("YZ", offset = -benchLength / 2 - dividerThickness / 2), benchLength + dividerThickness)
|
||||
seatSlats(offsetPlane(YZ, offset = -benchLength / 2 - (dividerThickness / 2)), benchLength + dividerThickness)
|
||||
|
||||
// Create the back slats
|
||||
backSlats(offsetPlane("YZ", offset = -benchLength / 2 - dividerThickness / 2), benchLength + dividerThickness)
|
||||
backSlats(offsetPlane(YZ, offset = -benchLength / 2 - (dividerThickness / 2)), benchLength + dividerThickness)
|
||||
|
||||
// Create the arm rests
|
||||
armRest("-YZ", benchLength / 2)
|
||||
armRest("-YZ", -benchLength / 2)
|
||||
armRest(-YZ, benchLength / 2)
|
||||
armRest(-YZ, -benchLength / 2)
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Shelf Bracket
|
||||
// This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided.
|
||||
|
||||
|
||||
// Define constants
|
||||
sigmaAllow = 35000 // psi (6061-T6 aluminum)
|
||||
width = 6 // inch
|
||||
@ -12,14 +11,16 @@ wallMountL = 2 // inches
|
||||
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
|
||||
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
|
||||
|
||||
|
||||
// Calculate required thickness of bracket
|
||||
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
|
||||
|
||||
|
||||
filletRadius = .25
|
||||
extFilletRadius = filletRadius + thickness
|
||||
mountingHoleDiameter = 0.5
|
||||
|
||||
sketch001 = startSketchOn('XZ')
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> xLine(length = shelfMountL - thickness, tag = $seg01)
|
||||
|> yLine(length = thickness, tag = $seg02)
|
||||
@ -29,48 +30,18 @@ sketch001 = startSketchOn('XZ')
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg06)
|
||||
|> close()
|
||||
|> extrude(%, length = width)
|
||||
|> fillet(
|
||||
radius = extFilletRadius,
|
||||
tags = [getNextAdjacentEdge(seg03)],
|
||||
)
|
||||
|> fillet(
|
||||
radius = filletRadius,
|
||||
tags = [getNextAdjacentEdge(seg06)],
|
||||
)
|
||||
|> fillet(
|
||||
radius = filletRadius,
|
||||
tags = [seg02, getOppositeEdge(seg02)],
|
||||
)
|
||||
|> fillet(
|
||||
radius = filletRadius,
|
||||
tags = [seg05, getOppositeEdge(seg05)],
|
||||
)
|
||||
|> fillet(radius = extFilletRadius, tags = [getNextAdjacentEdge(seg03)])
|
||||
|> fillet(radius = filletRadius, tags = [getNextAdjacentEdge(seg06)])
|
||||
|> fillet(radius = filletRadius, tags = [seg02, getOppositeEdge(seg02)])
|
||||
|> fillet(radius = filletRadius, tags = [seg05, getOppositeEdge(seg05)])
|
||||
|
||||
sketch002 = startSketchOn(sketch001, seg03)
|
||||
|> circle(
|
||||
center = [-1.25, 1],
|
||||
radius = mountingHoleDiameter / 2,
|
||||
)
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = 2.5,
|
||||
axis = [-1, 0],
|
||||
)
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = 4,
|
||||
axis = [0, 1],
|
||||
)
|
||||
|> extrude(%, length = -thickness-.01)
|
||||
|> circle(center = [-1.25, 1], radius = mountingHoleDiameter / 2)
|
||||
|> patternLinear2d(instances = 2, distance = 2.5, axis = [-1, 0])
|
||||
|> patternLinear2d(instances = 2, distance = 4, axis = [0, 1])
|
||||
|> extrude(%, length = -thickness - .01)
|
||||
|
||||
sketch003 = startSketchOn(sketch001, seg04)
|
||||
|> circle(
|
||||
center = [1, -1],
|
||||
radius = mountingHoleDiameter / 2,
|
||||
)
|
||||
|> patternLinear2d(
|
||||
instances = 2,
|
||||
distance = 4,
|
||||
axis = [1, 0],
|
||||
)
|
||||
|> extrude(%, length = -thickness-0.1)
|
||||
|> circle(center = [1, -1], radius = mountingHoleDiameter / 2)
|
||||
|> patternLinear2d(instances = 2, distance = 4, axis = [1, 0])
|
||||
|> extrude(%, length = -thickness - 0.1)
|
||||
|
@ -1,16 +1,14 @@
|
||||
// Brake Caliper
|
||||
// Brake calipers are used to squeeze the brake pads against the rotor, causing larger and larger amounts of friction depending on how hard the brakes are pressed.
|
||||
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
|
||||
// Import Constants
|
||||
import caliperTolerance, caliperPadLength, caliperThickness, caliperOuterEdgeRadius, caliperInnerEdgeRadius, rotorDiameter, rotorTotalThickness, yAxisOffset from "globals.kcl"
|
||||
|
||||
// Sketch the brake caliper profile
|
||||
brakeCaliperSketch = startSketchOn('XY')
|
||||
brakeCaliperSketch = startSketchOn(XY)
|
||||
|> startProfileAt([
|
||||
rotorDiameter / 2 + caliperTolerance,
|
||||
0
|
||||
|
@ -1,39 +1,28 @@
|
||||
// Wheel rotor
|
||||
// A component of a disc brake system. It provides a surface for brake pads to press against, generating the friction needed to slow or stop the vehicle.
|
||||
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
|
||||
// Import Constants
|
||||
import rotorDiameter, rotorInnerDiameter, rotorSinglePlateThickness, rotorInnerDiameterThickness, lugHolePatternDia, lugSpacing, rotorTotalThickness, spacerPatternDiameter, spacerDiameter, spacerLength, spacerCount, wheelDiameter, lugCount, yAxisOffset, drillAndSlotCount from "globals.kcl"
|
||||
|
||||
rotorSketch = startSketchOn('XZ')
|
||||
|> circle(
|
||||
center = [0, 0],
|
||||
radius = rotorDiameter / 2
|
||||
)
|
||||
rotorSketch = startSketchOn(XZ)
|
||||
|> circle(center = [0, 0], radius = rotorDiameter / 2)
|
||||
rotor = extrude(rotorSketch, length = rotorSinglePlateThickness)
|
||||
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
||||
|
||||
rotorBumpSketch = startSketchOn(rotor, 'end')
|
||||
|> circle(
|
||||
center = [0, 0],
|
||||
radius = rotorInnerDiameter / 2
|
||||
)
|
||||
|> circle(center = [0, 0], radius = rotorInnerDiameter / 2)
|
||||
rotorBump = extrude(rotorBumpSketch, length = rotorInnerDiameterThickness)
|
||||
|
||||
lugHoles = startSketchOn(rotorBump, 'end')
|
||||
|> circle(
|
||||
center = [-lugSpacing / 2, 0],
|
||||
radius = 0.315
|
||||
)
|
||||
|> circle(center = [-lugSpacing / 2, 0], radius = 0.315)
|
||||
|> patternCircular2d(
|
||||
arcDegrees = 360,
|
||||
center = [0, 0],
|
||||
instances = lugCount,
|
||||
rotateDuplicates = true
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> extrude(%, length = -(rotorInnerDiameterThickness + rotorSinglePlateThickness))
|
||||
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
||||
@ -44,35 +33,26 @@ centerSpacer = startSketchOn(rotor, 'start')
|
||||
|> extrude(%, length = spacerLength)
|
||||
|
||||
secondaryRotorSketch = startSketchOn(centerSpacer, 'end')
|
||||
|> circle(
|
||||
center = [0, 0],
|
||||
radius = rotorDiameter / 2
|
||||
)
|
||||
|> circle(center = [0, 0], radius = rotorDiameter / 2)
|
||||
secondRotor = extrude(secondaryRotorSketch, length = rotorSinglePlateThickness)
|
||||
|
||||
lugHoles2 = startSketchOn(secondRotor, 'end')
|
||||
|> circle(
|
||||
center = [-lugSpacing / 2, 0],
|
||||
radius = 0.315
|
||||
)
|
||||
|> circle(center = [-lugSpacing / 2, 0], radius = 0.315)
|
||||
|> patternCircular2d(
|
||||
arcDegrees = 360,
|
||||
center = [0, 0],
|
||||
instances = lugCount,
|
||||
rotateDuplicates = true
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> extrude(length = -rotorSinglePlateThickness)
|
||||
|
||||
spacerSketch = startSketchOn(rotor, 'start')
|
||||
|> circle(
|
||||
center = [spacerPatternDiameter / 2, 0],
|
||||
radius = spacerDiameter
|
||||
)
|
||||
|> circle(center = [spacerPatternDiameter / 2, 0], radius = spacerDiameter)
|
||||
|> patternCircular2d(
|
||||
arcDegrees = 360,
|
||||
center = [0, 0],
|
||||
instances = spacerCount,
|
||||
rotateDuplicates = true
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
spacers = extrude(spacerSketch, length = spacerLength)
|
||||
|
||||
@ -87,7 +67,7 @@ rotorSlottedSketch = startSketchOn(rotor, 'START')
|
||||
center = [0, 0],
|
||||
instances = drillAndSlotCount,
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
rotorSlotted = extrude(rotorSlottedSketch, length = -rotorSinglePlateThickness / 2)
|
||||
|
||||
@ -102,7 +82,7 @@ secondRotorSlottedSketch = startSketchOn(secondRotor, 'END')
|
||||
center = [0, 0],
|
||||
instances = drillAndSlotCount,
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
extrude(secondRotorSlottedSketch, length = -rotorSinglePlateThickness / 2)
|
||||
|