Compare commits
	
		
			136 Commits
		
	
	
		
			jtran/clea
			...
			pierremtb/
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fecf5c2ee7 | |||
| 8ef31a0be1 | |||
| 3adb42b5f2 | |||
| 20016b101e | |||
| 8d9dbf36c3 | |||
| 440704ed9f | |||
| 2261217a5d | |||
| 10da986649 | |||
| 10789d9c3c | |||
| 67cc4f5835 | |||
| 2692f2b73a | |||
| 965cb18059 | |||
| a022b8ef6c | |||
| 4d24bf7c94 | |||
| 9a537da183 | |||
| df81b76b8b | |||
| ac3f7ab712 | |||
| d531728675 | |||
| 1d78fc15ac | |||
| c32aebc8ad | |||
| 997ebce3eb | |||
| dac91d3b79 | |||
| 1eaf371b44 | |||
| 0698432abf | |||
| 54da18d8ab | |||
| 2fe5ef7034 | |||
| 16b5eeadb1 | |||
| 7be4001839 | |||
| ffb2559787 | |||
| 0592d3b5da | |||
| 31e4d60045 | |||
| c0817b00e4 | |||
| 4ea1d16fb6 | |||
| d049bf33e8 | |||
| 7b11047d07 | |||
| 412e9568f2 | |||
| 9be208e5e1 | |||
| 842ef5ede9 | |||
| 3f855d7bad | |||
| 0a1a6e50cf | |||
| d4e955289c | |||
| c147a219f4 | |||
| 38513a1e25 | |||
| c0c5c790ca | |||
| 8b60f75220 | |||
| f91ad4331f | |||
| 59103a2118 | |||
| 9737c2550a | |||
| bf9d01a8dd | |||
| 702e322f90 | |||
| e82830754d | |||
| 7806377a5a | |||
| 859afa2fd8 | |||
| 0a5f3093fc | |||
| b65f7939f6 | |||
| c35dea5e07 | |||
| fc66d4745f | |||
| b313d26c2a | |||
| 00b94ead62 | |||
| 0531ea1ce9 | |||
| 5f9a4887c1 | |||
| da7dfa16d8 | |||
| 363ae10658 | |||
| ac4a6c84cf | |||
| c6fad2e2dc | |||
| 013cb10961 | |||
| 6261083cb1 | |||
| 2b0ba37ed0 | |||
| 96174f3cf6 | |||
| aed62ff912 | |||
| 9334d64608 | |||
| 4fa7d2d8c8 | |||
| 3e615dfdbc | |||
| c9860af29f | |||
| 23a42f0195 | |||
| a77fa639f3 | |||
| 0a5ad7c95b | |||
| 4a654523d2 | |||
| 73a7e2bfd6 | |||
| eb0850fea9 | |||
| 029f76f273 | |||
| 28b5f7080c | |||
| 5b1dcfecd6 | |||
| f89d191425 | |||
| 2f4e4b62a8 | |||
| 5ebd5c8dbb | |||
| a9ceaf2678 | |||
| c8afd3399b | |||
| 5dda4828c6 | |||
| 72acab752c | |||
| 81df38ad1c | |||
| 0576a2bef1 | |||
| 4b2f6b4647 | |||
| 69edaa4183 | |||
| 2eb7c382bf | |||
| 38913ecb98 | |||
| debd06129f | |||
| d38bd342a0 | |||
| f026f10335 | |||
| 895d7ebc6d | |||
| 65edf17a44 | |||
| 0c2a0a8c07 | |||
| 97cef4d16c | |||
| 9358278f7b | |||
| a174e084d4 | |||
| df7246897a | |||
| 0c9f64dd7c | |||
| d2b9d3a058 | |||
| 7e54f08778 | |||
| d9c2dd376e | |||
| 275a2150e7 | |||
| 8b8feb8d68 | |||
| e21ef3f122 | |||
| 66834931aa | |||
| 06c1bcaf2e | |||
| fc7df7ecbe | |||
| 67cb7b33bb | |||
| ea74b94fac | |||
| 529833c63f | |||
| 92da86391a | |||
| e7cb390db4 | |||
| 8a66bbbdbd | |||
| 3c53babb50 | |||
| 474acb1c68 | |||
| 1c941112d7 | |||
| 6f1d718097 | |||
| 36957237c0 | |||
| da9cae98aa | |||
| 9ae025dc56 | |||
| 579ab23d78 | |||
| 4bef33e745 | |||
| ac7bd28c5a | |||
| d478d81156 | |||
| 3d27f0191b | |||
| 30c2acd18a | |||
| a83b4b2145 | 
| @ -1,3 +1,3 @@ | ||||
| [codespell] | ||||
| ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall | ||||
| skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts | ||||
| ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall,ser | ||||
| skip: **/target,node_modules,build,dist,./out,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./packages/codemirror-lang-kcl/test/all.test.ts,tsconfig.tsbuildinfo | ||||
|  | ||||
							
								
								
									
										28
									
								
								.eslintrc
									
									
									
									
									
								
							
							
						
						| @ -5,16 +5,32 @@ | ||||
|     }, | ||||
|     "plugins": [ | ||||
|       "css-modules", | ||||
|       "jest", | ||||
|       "jsx-a11y", | ||||
|       "react", | ||||
|       "react-hooks", | ||||
|       "suggest-no-throw", | ||||
|       "testing-library", | ||||
|       "@typescript-eslint" | ||||
|     ], | ||||
|     "extends": [ | ||||
|       "react-app", | ||||
|       "react-app/jest", | ||||
|       "plugin:css-modules/recommended" | ||||
|       "plugin:css-modules/recommended", | ||||
|       "plugin:jsx-a11y/recommended", | ||||
|       "plugin:react-hooks/recommended" | ||||
|     ], | ||||
|     "rules": { | ||||
|       "@typescript-eslint/no-floating-promises": "error", | ||||
|       "@typescript-eslint/no-misused-promises": "error", | ||||
|       "jsx-a11y/click-events-have-key-events": "off", | ||||
|       "jsx-a11y/no-autofocus": "off", | ||||
|       "jsx-a11y/no-noninteractive-element-interactions": "off", | ||||
|       "no-restricted-globals": [ | ||||
|         "error", | ||||
|         { | ||||
|           "name": "isNaN", | ||||
|           "message": "Use Number.isNaN() instead." | ||||
|         } | ||||
|       ], | ||||
|       "semi": [ | ||||
|         "error", | ||||
|         "never" | ||||
| @ -25,6 +41,9 @@ | ||||
|     "overrides": [ | ||||
|       { | ||||
|         "files": ["e2e/**/*.ts"], // Update the pattern based on your file structure | ||||
|         "extends": [ | ||||
|           "plugin:testing-library/react" | ||||
|         ], | ||||
|         "rules": { | ||||
|           "suggest-no-throw/suggest-no-throw": "off", | ||||
|           "testing-library/prefer-screen-queries": "off", | ||||
| @ -33,6 +52,9 @@ | ||||
|       }, | ||||
|       { | ||||
|         "files": ["src/**/*.test.ts"], | ||||
|         "extends": [ | ||||
|           "plugin:testing-library/react" | ||||
|         ], | ||||
|         "rules": { | ||||
|           "suggest-no-throw/suggest-no-throw": "off", | ||||
|         } | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/ci-cd-scripts/playwright-electron.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then | ||||
| fi | ||||
|  | ||||
| retry=1 | ||||
| max_retrys=4 | ||||
| max_retrys=5 | ||||
|  | ||||
| # retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues | ||||
| while [[ $retry -le $max_retrys ]]; do | ||||
|  | ||||
							
								
								
									
										55
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -5,24 +5,37 @@ | ||||
|  | ||||
| version: 2 | ||||
| updates: | ||||
|     - package-ecosystem: 'npm' # See documentation for possible values | ||||
|       directory: '/' # Location of package manifests | ||||
|       schedule: | ||||
|           interval: 'weekly' | ||||
|       reviewers: | ||||
|           - franknoirot | ||||
|           - irev-dev | ||||
|     - package-ecosystem: 'github-actions' # See documentation for possible values | ||||
|       directory: '/' # Location of package manifests | ||||
|       schedule: | ||||
|           interval: 'weekly' | ||||
|       reviewers: | ||||
|           - adamchalmers | ||||
|           - jessfraz | ||||
|     - package-ecosystem: 'cargo' # See documentation for possible values | ||||
|       directory: '/src/wasm-lib/' # Location of package manifests | ||||
|       schedule: | ||||
|           interval: 'weekly' | ||||
|       reviewers: | ||||
|           - adamchalmers | ||||
|           - jessfraz | ||||
|   - package-ecosystem: 'npm' # See documentation for possible values | ||||
|     directories: | ||||
|       - '/' | ||||
|       - '/packages/codemirror-lang-kcl/' | ||||
|       - '/packages/codemirror-lsp-client/' | ||||
|     schedule: | ||||
|       interval: weekly | ||||
|       day: monday | ||||
|     reviewers: | ||||
|       - franknoirot | ||||
|       - irev-dev | ||||
|   - package-ecosystem: 'github-actions' # See documentation for possible values | ||||
|     directory: '/' # Location of package manifests | ||||
|     schedule: | ||||
|       interval: weekly | ||||
|       day: monday | ||||
|     reviewers: | ||||
|       - adamchalmers | ||||
|       - jessfraz | ||||
|   - package-ecosystem: 'cargo' # See documentation for possible values | ||||
|     directory: '/src/wasm-lib/' # Location of package manifests | ||||
|     schedule: | ||||
|       interval: weekly | ||||
|       day: monday | ||||
|     reviewers: | ||||
|       - adamchalmers | ||||
|       - jessfraz | ||||
|     groups: | ||||
|       serde-dependencies: | ||||
|         patterns: | ||||
|           - "serde*" | ||||
|       wasm-bindgen-deps: | ||||
|         patterns: | ||||
|           - "wasm-bindgen*" | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/build-and-store-wasm.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -27,7 +27,7 @@ jobs: | ||||
|  | ||||
|  | ||||
|       # Upload the WASM bundle as an artifact | ||||
|       - uses: actions/upload-artifact@v3 | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: wasm-bundle | ||||
|           path: src/wasm-lib/pkg | ||||
|  | ||||
							
								
								
									
										24
									
								
								.github/workflows/build-apps.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -126,7 +126,13 @@ jobs: | ||||
|           node-version-file: '.nvmrc' | ||||
|           cache: 'yarn' # Set this to npm, yarn or pnpm. | ||||
|  | ||||
|       - run: yarn install | ||||
|       - name: yarn install | ||||
|         # Windows is picky sometimes and fails on fetch. Step takes about ~30s | ||||
|         uses: nick-fields/retry@v3.0.0 | ||||
|         with: | ||||
|           timeout_minutes: 2 | ||||
|           max_attempts: 3 | ||||
|           command: yarn install | ||||
|  | ||||
|       - run: yarn tronb:vite | ||||
|  | ||||
| @ -173,7 +179,13 @@ jobs: | ||||
|           CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | ||||
|           CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} | ||||
|           WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} | ||||
|         run: yarn electron-builder --config --publish always | ||||
|           DEBUG: "electron-notarize*" | ||||
|         # TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures | ||||
|         uses: nick-fields/retry@v3.0.0 | ||||
|         with: | ||||
|           timeout_minutes: 10 | ||||
|           max_attempts: 3 | ||||
|           command: yarn electron-builder --config --publish always | ||||
|  | ||||
|       - name: List artifacts in out/ | ||||
|         run: ls -R out | ||||
| @ -228,7 +240,13 @@ jobs: | ||||
|           CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | ||||
|           CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} | ||||
|           WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} | ||||
|         run: yarn electron-builder --config --publish always | ||||
|           DEBUG: "electron-notarize*" | ||||
|         # TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures | ||||
|         uses: nick-fields/retry@v3.0.0 | ||||
|         with: | ||||
|           timeout_minutes: 10 | ||||
|           max_attempts: 3 | ||||
|           command: yarn electron-builder --config --publish always | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|         if: ${{ env.IS_RELEASE == 'true' }} | ||||
|  | ||||
							
								
								
									
										44
									
								
								.github/workflows/cargo-bench.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,44 +0,0 @@ | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|     paths: | ||||
|       - '**.rs' | ||||
|       - '**/Cargo.toml' | ||||
|       - '**/Cargo.lock' | ||||
|       - '**/rust-toolchain.toml' | ||||
|       - .github/workflows/cargo-bench.yml | ||||
|   pull_request: | ||||
|     paths: | ||||
|       - '**.rs' | ||||
|       - '**/Cargo.toml' | ||||
|       - '**/Cargo.lock' | ||||
|       - '**/rust-toolchain.toml' | ||||
|       - .github/workflows/cargo-bench.yml | ||||
|   workflow_dispatch: | ||||
| permissions: read-all | ||||
| concurrency: | ||||
|   group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | ||||
|   cancel-in-progress: true | ||||
| name: cargo bench | ||||
| jobs: | ||||
|   cargo-bench: | ||||
|     name: Benchmark with iai | ||||
|     runs-on: ubuntu-latest-8-cores | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: dtolnay/rust-toolchain@stable | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           cargo install cargo-criterion | ||||
|           sudo apt update | ||||
|           sudo apt install -y valgrind | ||||
|       - name: Rust Cache | ||||
|         uses: Swatinem/rust-cache@v2.6.1 | ||||
|       - name: Benchmark kcl library | ||||
|         shell: bash | ||||
|         run: |- | ||||
|           cd src/wasm-lib/kcl; cargo bench --all-features -- iai | ||||
|         env: | ||||
|           KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} | ||||
|  | ||||
							
								
								
									
										32
									
								
								.github/workflows/codemirror-lang-kcl.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,32 @@ | ||||
| name: CodeMirror Lang KCL | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|  | ||||
| concurrency: | ||||
|   group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | ||||
|   cancel-in-progress: true | ||||
|  | ||||
| jobs: | ||||
|   yarn-unit-test: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|  | ||||
|       - uses: actions/setup-node@v4 | ||||
|         with: | ||||
|           node-version-file: '.nvmrc' | ||||
|           cache: 'yarn' | ||||
|  | ||||
|       - run: yarn install | ||||
|         working-directory: packages/codemirror-lang-kcl | ||||
|  | ||||
|       - run: yarn tsc | ||||
|         working-directory: packages/codemirror-lang-kcl | ||||
|  | ||||
|       - name: run unit tests | ||||
|         run: yarn test | ||||
|         working-directory: packages/codemirror-lang-kcl | ||||
							
								
								
									
										8
									
								
								.github/workflows/e2e-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -18,7 +18,6 @@ permissions: | ||||
| jobs: | ||||
|  | ||||
|   check-rust-changes: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     runs-on: ubuntu-latest | ||||
|     outputs: | ||||
|       rust-changed: ${{ steps.filter.outputs.rust }} | ||||
| @ -35,7 +34,6 @@ jobs: | ||||
|               - 'src/wasm-lib/**' | ||||
|  | ||||
|   electron: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     timeout-minutes: 60 | ||||
|     name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }} | ||||
|     strategy: | ||||
| @ -129,9 +127,12 @@ jobs: | ||||
|       shell: bash | ||||
|       run: yarn tron:package | ||||
|     - name: Run ubuntu/chrome snapshots | ||||
|       if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }} | ||||
|       shell: bash | ||||
|       # TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest, | ||||
|       # but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes. | ||||
|       run: | | ||||
|         PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot  --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} | ||||
|         PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot  --shard=1/1 | ||||
|       env: | ||||
|         CI: true | ||||
|         NODE_ENV: development | ||||
| @ -152,6 +153,7 @@ jobs: | ||||
|       continue-on-error: true | ||||
|       run: rm -r test-results | ||||
|     - name: check for changes | ||||
|       if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }} | ||||
|       shell: bash | ||||
|       id: git-check | ||||
|       run: | | ||||
|  | ||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @ -337,13 +337,47 @@ For individual testing: | ||||
| yarn test abstractSyntaxTree -t "unexpected closed curly brace" --silent=false | ||||
| ``` | ||||
|  | ||||
| Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default. | ||||
| Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro) tests, in interactive mode by default. | ||||
|  | ||||
| ### Rust tests | ||||
|  | ||||
| ```bash | ||||
| **Dependencies** | ||||
|  | ||||
| - `KITTYCAD_API_TOKEN` | ||||
| - `cargo-nextest` | ||||
| - `just` | ||||
|  | ||||
| #### Setting KITTYCAD_API_TOKEN | ||||
| Use the production zoo.dev token, set this environment variable before running the tests | ||||
|  | ||||
| #### Installing cargonextest | ||||
|  | ||||
| ``` | ||||
| cd src/wasm-lib | ||||
| KITTYCAD_API_TOKEN=XXX cargo test -- --test-threads=1 | ||||
| cargo search cargo-nextest | ||||
| cargo install cargo-nextest | ||||
| ``` | ||||
|  | ||||
| #### just | ||||
| install [`just`](https://github.com/casey/just?tab=readme-ov-file#pre-built-binaries) | ||||
|  | ||||
| #### Running the tests | ||||
|  | ||||
| ```bash | ||||
| # With just | ||||
| # Make sure KITTYCAD_API_TOKEN=<prod zoo.dev token> is set | ||||
| # Make sure you installed cargo-nextest | ||||
| # Make sure you installed just | ||||
| cd src/wasm-lib | ||||
| just test | ||||
| ``` | ||||
|  | ||||
| ```bash | ||||
| # Without just | ||||
| # Make sure KITTYCAD_API_TOKEN=<prod zoo.dev token> is set | ||||
| # Make sure you installed cargo-nextest | ||||
| cd src/wasm-lib | ||||
| export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1 | ||||
| ``` | ||||
|  | ||||
| Where `XXX` is an API token from the production engine (NOT the dev environment). | ||||
|  | ||||
							
								
								
									
										42
									
								
								docs/kcl/circleThreePoint.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										43
									
								
								docs/kcl/helixRevolutions.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -35,6 +35,7 @@ layout: manual | ||||
| * [`ceil`](kcl/ceil) | ||||
| * [`chamfer`](kcl/chamfer) | ||||
| * [`circle`](kcl/circle) | ||||
| * [`circleThreePoint`](kcl/circleThreePoint) | ||||
| * [`close`](kcl/close) | ||||
| * [`cm`](kcl/cm) | ||||
| * [`cos`](kcl/cos) | ||||
| @ -47,11 +48,11 @@ layout: manual | ||||
| * [`getOppositeEdge`](kcl/getOppositeEdge) | ||||
| * [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge) | ||||
| * [`helix`](kcl/helix) | ||||
| * [`helixRevolutions`](kcl/helixRevolutions) | ||||
| * [`hole`](kcl/hole) | ||||
| * [`hollow`](kcl/hollow) | ||||
| * [`import`](kcl/import) | ||||
| * [`inch`](kcl/inch) | ||||
| * [`int`](kcl/int) | ||||
| * [`lastSegX`](kcl/lastSegX) | ||||
| * [`lastSegY`](kcl/lastSegY) | ||||
| * [`legAngX`](kcl/legAngX) | ||||
| @ -80,6 +81,7 @@ layout: manual | ||||
| * [`pi`](kcl/pi) | ||||
| * [`polar`](kcl/polar) | ||||
| * [`polygon`](kcl/polygon) | ||||
| * [`pop`](kcl/pop) | ||||
| * [`pow`](kcl/pow) | ||||
| * [`profileStart`](kcl/profileStart) | ||||
| * [`profileStartX`](kcl/profileStartX) | ||||
|  | ||||
| @ -4,6 +4,8 @@ excerpt: "Convert a number to an integer." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| **WARNING:** This function is deprecated. | ||||
|  | ||||
| Convert a number to an integer. | ||||
|  | ||||
| DEPRECATED use floor(), ceil(), or round(). | ||||
|  | ||||
							
								
								
									
										39
									
								
								docs/kcl/pop.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										29571
									
								
								docs/kcl/std.json
									
									
									
									
									
								
							
							
						
						| @ -1,19 +1,19 @@ | ||||
| --- | ||||
| title: "AxisOrEdgeReference" | ||||
| excerpt: "Axis or tagged edge." | ||||
| title: "Axis2dOrEdgeReference" | ||||
| excerpt: "A 2D axis or tagged edge." | ||||
| layout: manual | ||||
| --- | ||||
| 
 | ||||
| Axis or tagged edge. | ||||
| A 2D axis or tagged edge. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| **This schema accepts any of the following:** | ||||
| 
 | ||||
| Axis and origin. | ||||
| 2D axis and origin. | ||||
| 
 | ||||
| [`AxisAndOrigin`](/docs/kcl/types/AxisAndOrigin) | ||||
| [`AxisAndOrigin2d`](/docs/kcl/types/AxisAndOrigin2d) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										42
									
								
								docs/kcl/types/Axis3dOrEdgeReference.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,42 @@ | ||||
| --- | ||||
| title: "Axis3dOrEdgeReference" | ||||
| excerpt: "A 3D axis or tagged edge." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| A 3D axis or tagged edge. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| **This schema accepts any of the following:** | ||||
|  | ||||
| 3D axis and origin. | ||||
|  | ||||
| [`AxisAndOrigin3d`](/docs/kcl/types/AxisAndOrigin3d) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
| Tagged edge. | ||||
|  | ||||
| [`EdgeReference`](/docs/kcl/types/EdgeReference) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| --- | ||||
| title: "AxisAndOrigin" | ||||
| excerpt: "Axis and origin." | ||||
| title: "AxisAndOrigin2d" | ||||
| excerpt: "A 2D axis and origin." | ||||
| layout: manual | ||||
| --- | ||||
| 
 | ||||
| Axis and origin. | ||||
| A 2D axis and origin. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										105
									
								
								docs/kcl/types/AxisAndOrigin3d.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,105 @@ | ||||
| --- | ||||
| title: "AxisAndOrigin3d" | ||||
| excerpt: "A 3D axis and origin." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| A 3D axis and origin. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| **This schema accepts exactly one of the following:** | ||||
|  | ||||
| X-axis. | ||||
|  | ||||
| **enum:** `X` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
| Y-axis. | ||||
|  | ||||
| **enum:** `Y` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
| Z-axis. | ||||
|  | ||||
| **enum:** `Z` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
| Flip the X-axis. | ||||
|  | ||||
| **enum:** `-X` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
| Flip the Y-axis. | ||||
|  | ||||
| **enum:** `-Y` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
| Flip the Z-axis. | ||||
|  | ||||
| **enum:** `-Z` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `custom` |`object`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										23
									
								
								docs/kcl/types/CircleThreePointData.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,23 @@ | ||||
| --- | ||||
| title: "CircleThreePointData" | ||||
| excerpt: "Data for drawing a 3-point circle" | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Data for drawing a 3-point circle | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `p1` |`[number, number]`| Point one for circle derivation. | No | | ||||
| | `p2` |`[number, number]`| Point two for circle derivation. | No | | ||||
| | `p3` |`[number, number]`| Point three for circle derivation. | No | | ||||
|  | ||||
|  | ||||
							
								
								
									
										28
									
								
								docs/kcl/types/Face.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,28 @@ | ||||
| --- | ||||
| title: "Face" | ||||
| excerpt: "A face." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| A face. | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `id` |`string`| The id of the face. | No | | ||||
| | `value` |`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 | | ||||
| | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | ||||
| | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A face. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||
|  | ||||
|  | ||||
							
								
								
									
										26
									
								
								docs/kcl/types/Helix.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,26 @@ | ||||
| --- | ||||
| title: "Helix" | ||||
| excerpt: "A helix." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| A helix. | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `value` |`string`| The id of the helix. | No | | ||||
| | `revolutions` |`number`| Number of revolutions. | No | | ||||
| | `angleStart` |`number`| Start angle (in degrees). | No | | ||||
| | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||
|  | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| --- | ||||
| title: "HelixData" | ||||
| excerpt: "Data for helices." | ||||
| excerpt: "Data for a helix." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Data for helices. | ||||
| Data for a helix. | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
| @ -19,6 +19,8 @@ Data for helices. | ||||
| | `revolutions` |`number`| Number of revolutions. | No | | ||||
| | `angleStart` |`number`| Start angle (in degrees). | No | | ||||
| | `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No | | ||||
| | `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No | | ||||
| | `length` |`number`| Length of the helix. This is not necessary if the helix is created around an edge. If not given the length of the edge is used. | No | | ||||
| | `radius` |`number`| Radius of the helix. | No | | ||||
| | `axis` |[`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference)| Axis to use as mirror. | No | | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										24
									
								
								docs/kcl/types/HelixRevolutionsData.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,24 @@ | ||||
| --- | ||||
| title: "HelixRevolutionsData" | ||||
| excerpt: "Data for helix revolutions." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Data for helix revolutions. | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `revolutions` |`number`| Number of revolutions. | No | | ||||
| | `angleStart` |`number`| Start angle (in degrees). | No | | ||||
| | `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No | | ||||
| | `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No | | ||||
|  | ||||
|  | ||||
							
								
								
									
										26
									
								
								docs/kcl/types/HelixValue.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,26 @@ | ||||
| --- | ||||
| title: "HelixValue" | ||||
| excerpt: "A helix." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| A helix. | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `value` |`string`| The id of the helix. | No | | ||||
| | `revolutions` |`number`| Number of revolutions. | No | | ||||
| | `angleStart` |`number`| Start angle (in degrees). | No | | ||||
| | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||
|  | ||||
|  | ||||
| @ -168,7 +168,6 @@ Any KCL value. | ||||
|  | ||||
|  | ||||
| ---- | ||||
| A plane. | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
| @ -181,17 +180,10 @@ A plane. | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: [`Plane`](/docs/kcl/types/Plane)|  | No | | ||||
| | `id` |`string`| The id of the plane. | No | | ||||
| | `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | 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 | | ||||
| | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No | | ||||
| | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||
| | `value` |[`Plane`](/docs/kcl/types/Plane)| Any KCL value. | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
| A face. | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
| @ -203,14 +195,8 @@ A face. | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `Face`|  | No | | ||||
| | `id` |`string`| The id of the face. | No | | ||||
| | `value` |`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 | | ||||
| | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | ||||
| | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||
| | `type` |enum: [`Face`](/docs/kcl/types/Face)|  | No | | ||||
| | `value` |[`Face`](/docs/kcl/types/Face)| Any KCL value. | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
| @ -246,7 +232,6 @@ A face. | ||||
|  | ||||
|  | ||||
| ---- | ||||
| An solid is a collection of extrude surfaces. | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
| @ -259,14 +244,7 @@ An solid is a collection of extrude surfaces. | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: [`Solid`](/docs/kcl/types/Solid)|  | No | | ||||
| | `id` |`string`| The id of the solid. | No | | ||||
| | `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No | | ||||
| | `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No | | ||||
| | `height` |`number`| The height of the solid. | No | | ||||
| | `startCapId` |`string`| The id of the extrusion start cap | No | | ||||
| | `endCapId` |`string`| The id of the extrusion end cap | No | | ||||
| | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | ||||
| | `value` |[`Solid`](/docs/kcl/types/Solid)| Any KCL value. | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
| @ -285,6 +263,22 @@ An solid is a collection of extrude surfaces. | ||||
| | `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: [`Helix`](/docs/kcl/types/Helix)|  | No | | ||||
| | `value` |[`Helix`](/docs/kcl/types/Helix)| Any KCL value. | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
| Data for an imported geometry. | ||||
|  | ||||
|  | ||||
| @ -16,6 +16,6 @@ Data for a mirror. | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis to use as mirror. | No | | ||||
| | `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis to use as mirror. | No | | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -22,6 +22,7 @@ A plane. | ||||
| | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No | | ||||
| | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No | | ||||
| | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A plane. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -17,7 +17,7 @@ Data for revolution surfaces. | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `angle` |`number` (**maximum:** 360.0) (**minimum:** -360.0)| Angle to revolve (in degrees). Default is 360. | No | | ||||
| | `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis of revolution. | No | | ||||
| | `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis of revolution. | No | | ||||
| | `tolerance` |`number`| Tolerance for the revolve operation. | No | | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -21,6 +21,7 @@ A sketch is a collection of paths. | ||||
| | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | ||||
| | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | ||||
| | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch is a collection of paths. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -30,6 +30,7 @@ A sketch is a collection of paths. | ||||
| | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | ||||
| | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | ||||
| | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch or a group of sketches. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -31,6 +31,7 @@ A plane. | ||||
| | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No | | ||||
| | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No | | ||||
| | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||
|  | ||||
|  | ||||
| @ -54,6 +55,7 @@ A face. | ||||
| | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No | | ||||
| | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | ||||
| | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -23,6 +23,7 @@ An solid is a collection of extrude surfaces. | ||||
| | `startCapId` |`string`| The id of the extrusion start cap | No | | ||||
| | `endCapId` |`string`| The id of the extrusion end cap | No | | ||||
| | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| An solid is a collection of extrude surfaces. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -32,6 +32,7 @@ An solid is a collection of extrude surfaces. | ||||
| | `startCapId` |`string`| The id of the extrusion start cap | No | | ||||
| | `endCapId` |`string`| The id of the extrusion end cap | No | | ||||
| | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A solid or a group of solids. | No | | ||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -16,7 +16,7 @@ Data for a sweep. | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No | | ||||
| | `path` |[`SweepPath`](/docs/kcl/types/SweepPath)| The path to sweep along. | No | | ||||
| | `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No | | ||||
| | `tolerance` |`number`| Tolerance for the sweep operation. | No | | ||||
|  | ||||
|  | ||||
							
								
								
									
										42
									
								
								docs/kcl/types/SweepPath.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,42 @@ | ||||
| --- | ||||
| title: "SweepPath" | ||||
| excerpt: "A path to sweep along." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| A path to sweep along. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| **This schema accepts any of the following:** | ||||
|  | ||||
| A path to sweep along. | ||||
|  | ||||
| [`Sketch`](/docs/kcl/types/Sketch) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
| A path to sweep along. | ||||
|  | ||||
| [`Helix`](/docs/kcl/types/Helix) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										107
									
								
								docs/kcl/types/UnitLen.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,107 @@ | ||||
| --- | ||||
| title: "UnitLen" | ||||
| excerpt: "" | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| **This schema accepts exactly one of the following:** | ||||
|  | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `Mm`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `Cm`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `M`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `Inches`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `Feet`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `Yards`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -280,7 +280,7 @@ test( | ||||
|  | ||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||
|       await expect(page.getByText('router-template-slate')).toBeVisible() | ||||
|       await expect(page.getByText('New Project')).toBeVisible() | ||||
|       await expect(page.getByText('Create project')).toBeVisible() | ||||
|     }) | ||||
|  | ||||
|     await test.step('Opening the router-template project should load', async () => { | ||||
|  | ||||
| @ -45,46 +45,6 @@ test.describe('Command bar tests', () => { | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   // TODO: fix this test after the electron migration | ||||
|   test.fixme('Fillet from command bar', async ({ page, homePage }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-5, -5], %) | ||||
|     |> line([0, 10], %) | ||||
|     |> line([10, 0], %) | ||||
|     |> line([0, -10], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(-10, sketch001)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     const selectSegment = () => page.getByText(`line([0, -10], %)`).click() | ||||
|  | ||||
|     await selectSegment() | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.getByRole('button', { name: 'Fillet' }).click() | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.keyboard.press('Enter') // skip selection | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.keyboard.press('Enter') // accept default radius | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.keyboard.press('Enter') // submit | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-activeLine')).toContainText( | ||||
|       `fillet({ radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] }, %)` | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('Command bar can change a setting, and switch back and forth between arguments', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|  | ||||
| @ -38,14 +38,14 @@ test.describe('Debug pane', () => { | ||||
|       // Set the code in the code editor. | ||||
|       await u.codeLocator.click() | ||||
|       await page.keyboard.type(code, { delay: 0 }) | ||||
|       // Scroll to the feature tree. | ||||
|       // Scroll to the artifact graph. | ||||
|       await tree.scrollIntoViewIfNeeded() | ||||
|       // Expand the feature tree. | ||||
|       await tree.getByText('Feature Tree').click() | ||||
|       // Expand the artifact graph. | ||||
|       await tree.getByText('Artifact Graph').click() | ||||
|       // Just expanded the details, making the element taller, so scroll again. | ||||
|       await tree.getByText('Plane').first().scrollIntoViewIfNeeded() | ||||
|     }) | ||||
|     // Extract the artifact IDs from the debug feature tree. | ||||
|     // Extract the artifact IDs from the debug artifact graph. | ||||
|     const initialSegmentIds = await segment.innerText({ timeout: 5_000 }) | ||||
|     // The artifact ID should include a UUID. | ||||
|     expect(initialSegmentIds).toMatch( | ||||
|  | ||||
| @ -135,4 +135,20 @@ export class CmdBarFixture { | ||||
|       await promptEditCommand.first().click() | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   get cmdSearchInput() { | ||||
|     return this.page.getByTestId('cmd-bar-search') | ||||
|   } | ||||
|  | ||||
|   get argumentInput() { | ||||
|     return this.page.getByTestId('cmd-bar-arg-value') | ||||
|   } | ||||
|  | ||||
|   get cmdOptions() { | ||||
|     return this.page.getByTestId('cmd-bar-option') | ||||
|   } | ||||
|  | ||||
|   chooseCommand = async (commandName: string) => { | ||||
|     await this.cmdOptions.getByText(commandName).click() | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -121,18 +121,23 @@ export class AuthenticatedTronApp { | ||||
|  | ||||
| export const fixtures = { | ||||
|   cmdBar: async ({ page }: { page: Page }, use: any) => { | ||||
|     // eslint-disable-next-line react-hooks/rules-of-hooks | ||||
|     await use(new CmdBarFixture(page)) | ||||
|   }, | ||||
|   editor: async ({ page }: { page: Page }, use: any) => { | ||||
|     // eslint-disable-next-line react-hooks/rules-of-hooks | ||||
|     await use(new EditorFixture(page)) | ||||
|   }, | ||||
|   toolbar: async ({ page }: { page: Page }, use: any) => { | ||||
|     // eslint-disable-next-line react-hooks/rules-of-hooks | ||||
|     await use(new ToolbarFixture(page)) | ||||
|   }, | ||||
|   scene: async ({ page }: { page: Page }, use: any) => { | ||||
|     // eslint-disable-next-line react-hooks/rules-of-hooks | ||||
|     await use(new SceneFixture(page)) | ||||
|   }, | ||||
|   homePage: async ({ page }: { page: Page }, use: any) => { | ||||
|     // eslint-disable-next-line react-hooks/rules-of-hooks | ||||
|     await use(new HomePageFixture(page)) | ||||
|   }, | ||||
| } | ||||
|  | ||||
| @ -103,7 +103,7 @@ export class HomePageFixture { | ||||
|       .toEqual(expectedState) | ||||
|   } | ||||
|  | ||||
|   createAndGoToProject = async (projectTitle: string) => { | ||||
|   createAndGoToProject = async (projectTitle = 'project-$nnn') => { | ||||
|     await expect(this.projectSection).not.toHaveText('Loading your Projects...') | ||||
|     await this.projectButtonNew.click() | ||||
|     await this.projectTextName.click() | ||||
|  | ||||
| @ -36,7 +36,8 @@ type DragFromHandler = ( | ||||
|  | ||||
| export class SceneFixture { | ||||
|   public page: Page | ||||
|  | ||||
|   public streamWrapper!: Locator | ||||
|   public loadingIndicator!: Locator | ||||
|   private exeIndicator!: Locator | ||||
|  | ||||
|   constructor(page: Page) { | ||||
| @ -64,6 +65,8 @@ export class SceneFixture { | ||||
|     this.page = page | ||||
|  | ||||
|     this.exeIndicator = page.getByTestId('model-state-indicator-execution-done') | ||||
|     this.streamWrapper = page.getByTestId('stream') | ||||
|     this.loadingIndicator = this.streamWrapper.getByTestId('loading') | ||||
|   } | ||||
|  | ||||
|   makeMouseHelpers = ( | ||||
|  | ||||
| @ -14,6 +14,9 @@ export class ToolbarFixture { | ||||
|  | ||||
|   extrudeButton!: Locator | ||||
|   loftButton!: Locator | ||||
|   sweepButton!: Locator | ||||
|   filletButton!: Locator | ||||
|   chamferButton!: Locator | ||||
|   shellButton!: Locator | ||||
|   offsetPlaneButton!: Locator | ||||
|   startSketchBtn!: Locator | ||||
| @ -40,6 +43,9 @@ export class ToolbarFixture { | ||||
|     this.page = page | ||||
|     this.extrudeButton = page.getByTestId('extrude') | ||||
|     this.loftButton = page.getByTestId('loft') | ||||
|     this.sweepButton = page.getByTestId('sweep') | ||||
|     this.filletButton = page.getByTestId('fillet3d') | ||||
|     this.chamferButton = page.getByTestId('chamfer3d') | ||||
|     this.shellButton = page.getByTestId('shell') | ||||
|     this.offsetPlaneButton = page.getByTestId('plane-offset') | ||||
|     this.startSketchBtn = page.getByTestId('sketch') | ||||
| @ -57,6 +63,10 @@ export class ToolbarFixture { | ||||
|     this.exeIndicator = page.getByTestId('model-state-indicator-execution-done') | ||||
|   } | ||||
|  | ||||
|   get logoLink() { | ||||
|     return this.page.getByTestId('app-logo') | ||||
|   } | ||||
|  | ||||
|   startSketchPlaneSelection = async () => | ||||
|     doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500) | ||||
|  | ||||
|  | ||||
| @ -756,6 +756,17 @@ test(`Offset plane point-and-click`, async ({ | ||||
|     }) | ||||
|     await scene.expectPixelColor([74, 74, 74], testPoint, 15) | ||||
|   }) | ||||
|  | ||||
|   await test.step('Delete offset plane via feature tree selection', async () => { | ||||
|     await editor.closePane() | ||||
|     const operationButton = await toolbar.getFeatureTreeOperation( | ||||
|       'Offset Plane', | ||||
|       0 | ||||
|     ) | ||||
|     await operationButton.click({ button: 'left' }) | ||||
|     await page.keyboard.press('Backspace') | ||||
|     await scene.expectPixelColor([50, 51, 96], testPoint, 15) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| const loftPointAndClickCases = [ | ||||
| @ -818,12 +829,6 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => { | ||||
|         }) | ||||
|         await selectSketches() | ||||
|         await cmdBar.progressCmdBar() | ||||
|         await cmdBar.expectState({ | ||||
|           stage: 'review', | ||||
|           headerArguments: { Selection: '2 faces' }, | ||||
|           commandName: 'Loft', | ||||
|         }) | ||||
|         await cmdBar.progressCmdBar() | ||||
|       }) | ||||
|     } else { | ||||
|       await test.step(`Preselect the two sketches`, async () => { | ||||
| @ -833,12 +838,6 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => { | ||||
|       await test.step(`Go through the command bar flow with preselected sketches`, async () => { | ||||
|         await toolbar.loftButton.click() | ||||
|         await cmdBar.progressCmdBar() | ||||
|         await cmdBar.expectState({ | ||||
|           stage: 'review', | ||||
|           headerArguments: { Selection: '2 faces' }, | ||||
|           commandName: 'Loft', | ||||
|         }) | ||||
|         await cmdBar.progressCmdBar() | ||||
|       }) | ||||
|     } | ||||
|  | ||||
| @ -851,6 +850,666 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => { | ||||
|       }) | ||||
|       await scene.expectPixelColor([89, 89, 89], testPoint, 15) | ||||
|     }) | ||||
|  | ||||
|     await test.step('Delete loft via feature tree selection', async () => { | ||||
|       await editor.closePane() | ||||
|       const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0) | ||||
|       await operationButton.click({ button: 'left' }) | ||||
|       await page.keyboard.press('Backspace') | ||||
|       await scene.expectPixelColor([254, 254, 254], testPoint, 15) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| // TODO: merge with above test. Right now we're not able to delete a loft | ||||
| // right after creation via selection for some reason, so we go with a new instance | ||||
| test('Loft and offset plane deletion via selection', async ({ | ||||
|   context, | ||||
|   page, | ||||
|   homePage, | ||||
|   scene, | ||||
| }) => { | ||||
|   const initialCode = `sketch001 = startSketchOn('XZ') | ||||
|   |> circle({ center = [0, 0], radius = 30 }, %) | ||||
|   plane001 = offsetPlane('XZ', 50) | ||||
|   sketch002 = startSketchOn(plane001) | ||||
|   |> circle({ center = [0, 0], radius = 20 }, %) | ||||
| loft001 = loft([sketch001, sketch002]) | ||||
| ` | ||||
|   await context.addInitScript((initialCode) => { | ||||
|     localStorage.setItem('persistCode', initialCode) | ||||
|   }, initialCode) | ||||
|   await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|   await homePage.goToModelingScene() | ||||
|  | ||||
|   // One dumb hardcoded screen pixel value | ||||
|   const testPoint = { x: 575, y: 200 } | ||||
|   const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||
|   const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 80) | ||||
|  | ||||
|   await test.step(`Delete loft`, async () => { | ||||
|     // Check for loft | ||||
|     await scene.expectPixelColor([89, 89, 89], testPoint, 15) | ||||
|     await clickOnSketch1() | ||||
|     await expect(page.locator('.cm-activeLine')).toHaveText(` | ||||
|       |> circle({ center = [0, 0], radius = 30 }, %) | ||||
|     `) | ||||
|     await page.keyboard.press('Backspace') | ||||
|     // Check for sketch 1 | ||||
|     await scene.expectPixelColor([254, 254, 254], testPoint, 15) | ||||
|   }) | ||||
|  | ||||
|   await test.step('Delete sketch002', async () => { | ||||
|     await page.waitForTimeout(1000) | ||||
|     await clickOnSketch2() | ||||
|     await expect(page.locator('.cm-activeLine')).toHaveText(` | ||||
|       |> circle({ center = [0, 0], radius = 20 }, %) | ||||
|     `) | ||||
|     await page.keyboard.press('Backspace') | ||||
|     // Check for plane001 | ||||
|     await scene.expectPixelColor([228, 228, 228], testPoint, 15) | ||||
|   }) | ||||
|  | ||||
|   await test.step('Delete plane001', async () => { | ||||
|     await page.waitForTimeout(1000) | ||||
|     await clickOnSketch2() | ||||
|     await expect(page.locator('.cm-activeLine')).toHaveText(` | ||||
|       plane001 = offsetPlane('XZ', 50) | ||||
|     `) | ||||
|     await page.keyboard.press('Backspace') | ||||
|     // Check for sketch 1 | ||||
|     await scene.expectPixelColor([254, 254, 254], testPoint, 15) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test(`Sweep point-and-click`, async ({ | ||||
|   context, | ||||
|   page, | ||||
|   homePage, | ||||
|   scene, | ||||
|   editor, | ||||
|   toolbar, | ||||
|   cmdBar, | ||||
| }) => { | ||||
|   const initialCode = `sketch001 = startSketchOn('YZ') | ||||
|   |> circle({ | ||||
|        center = [0, 0], | ||||
|        radius = 500 | ||||
|      }, %) | ||||
| sketch002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> xLine(-500, %) | ||||
|   |> tangentialArcTo([-2000, 500], %) | ||||
| ` | ||||
|   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: 700, y: 250 } | ||||
|   const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||
|   const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y) | ||||
|   const sweepDeclaration = 'sweep001 = sweep({ path = sketch002 }, sketch001)' | ||||
|  | ||||
|   await test.step(`Look for sketch001`, async () => { | ||||
|     await toolbar.closePane('code') | ||||
|     await scene.expectPixelColor([53, 53, 53], testPoint, 15) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Go through the command bar flow`, async () => { | ||||
|     await toolbar.sweepButton.click() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Sweep', | ||||
|       currentArgKey: 'target', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Target: '', | ||||
|         Trajectory: '', | ||||
|       }, | ||||
|       highlightedHeaderArg: 'target', | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await clickOnSketch1() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Sweep', | ||||
|       currentArgKey: 'trajectory', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Target: '1 face', | ||||
|         Trajectory: '', | ||||
|       }, | ||||
|       highlightedHeaderArg: 'trajectory', | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await clickOnSketch2() | ||||
|     await page.waitForTimeout(500) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await page.waitForTimeout(500) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Confirm code is added to the editor, scene has changed`, async () => { | ||||
|     await scene.expectPixelColor([135, 64, 73], testPoint, 15) | ||||
|     await toolbar.openPane('code') | ||||
|     await editor.expectEditor.toContain(sweepDeclaration) | ||||
|     await editor.expectState({ | ||||
|       diagnostics: [], | ||||
|       activeLines: [sweepDeclaration], | ||||
|       highlightedCode: '', | ||||
|     }) | ||||
|     await toolbar.closePane('code') | ||||
|   }) | ||||
|  | ||||
|   await test.step('Delete sweep via feature tree selection', async () => { | ||||
|     await toolbar.openPane('feature-tree') | ||||
|     await page.waitForTimeout(500) | ||||
|     const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0) | ||||
|     await operationButton.click({ button: 'left' }) | ||||
|     await page.keyboard.press('Backspace') | ||||
|     await page.waitForTimeout(500) | ||||
|     await toolbar.closePane('feature-tree') | ||||
|     await scene.expectPixelColor([53, 53, 53], testPoint, 15) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test(`Sweep point-and-click failing validation`, async ({ | ||||
|   context, | ||||
|   page, | ||||
|   homePage, | ||||
|   scene, | ||||
|   toolbar, | ||||
|   cmdBar, | ||||
| }) => { | ||||
|   const initialCode = `sketch001 = startSketchOn('YZ') | ||||
|   |> circle({ | ||||
|        center = [0, 0], | ||||
|        radius = 500 | ||||
|      }, %) | ||||
| sketch002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> xLine(-500, %) | ||||
|   |> lineTo([-2000, 500], %) | ||||
| ` | ||||
|   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: 700, y: 250 } | ||||
|   const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||
|   const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y) | ||||
|  | ||||
|   await test.step(`Look for sketch001`, async () => { | ||||
|     await toolbar.closePane('code') | ||||
|     await scene.expectPixelColor([53, 53, 53], testPoint, 15) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Go through the command bar flow and fail validation with a toast`, async () => { | ||||
|     await toolbar.sweepButton.click() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Sweep', | ||||
|       currentArgKey: 'target', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Target: '', | ||||
|         Trajectory: '', | ||||
|       }, | ||||
|       highlightedHeaderArg: 'target', | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await clickOnSketch1() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Sweep', | ||||
|       currentArgKey: 'trajectory', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Target: '1 face', | ||||
|         Trajectory: '', | ||||
|       }, | ||||
|       highlightedHeaderArg: 'trajectory', | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await clickOnSketch2() | ||||
|     await page.waitForTimeout(500) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await expect( | ||||
|       page.getByText('Unable to sweep with the provided selection') | ||||
|     ).toBeVisible() | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test(`Fillet point-and-click`, async ({ | ||||
|   context, | ||||
|   page, | ||||
|   homePage, | ||||
|   scene, | ||||
|   editor, | ||||
|   toolbar, | ||||
|   cmdBar, | ||||
| }) => { | ||||
|   // Code samples | ||||
|   const initialCode = `sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-12, -6], %) | ||||
|   |> line([0, 12], %) | ||||
|   |> line([24, 0], %) | ||||
|   |> line([0, -12], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(-12, sketch001) | ||||
| ` | ||||
|   const firstFilletDeclaration = 'fillet({ radius = 5, tags = [seg01] }, %)' | ||||
|   const secondFilletDeclaration = | ||||
|     'fillet({       radius = 5,       tags = [getOppositeEdge(seg01)]     }, %)' | ||||
|  | ||||
|   // Locators | ||||
|   const firstEdgeLocation = { x: 600, y: 193 } | ||||
|   const secondEdgeLocation = { x: 600, y: 383 } | ||||
|   const bodyLocation = { x: 630, y: 290 } | ||||
|   const [clickOnFirstEdge] = scene.makeMouseHelpers( | ||||
|     firstEdgeLocation.x, | ||||
|     firstEdgeLocation.y | ||||
|   ) | ||||
|   const [clickOnSecondEdge] = scene.makeMouseHelpers( | ||||
|     secondEdgeLocation.x, | ||||
|     secondEdgeLocation.y | ||||
|   ) | ||||
|  | ||||
|   // Colors | ||||
|   const edgeColorWhite: [number, number, number] = [248, 248, 248] | ||||
|   const edgeColorYellow: [number, number, number] = [251, 251, 40] // Mac:B=67 Ubuntu:B=12 | ||||
|   const bodyColor: [number, number, number] = [155, 155, 155] | ||||
|   const filletColor: [number, number, number] = [127, 127, 127] | ||||
|   const backgroundColor: [number, number, number] = [30, 30, 30] | ||||
|   const lowTolerance = 20 | ||||
|   const highTolerance = 40 | ||||
|  | ||||
|   // Setup | ||||
|   await test.step(`Initial test setup`, async () => { | ||||
|     await context.addInitScript((initialCode) => { | ||||
|       localStorage.setItem('persistCode', initialCode) | ||||
|     }, initialCode) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // verify modeling scene is loaded | ||||
|     await scene.expectPixelColor( | ||||
|       backgroundColor, | ||||
|       secondEdgeLocation, | ||||
|       lowTolerance | ||||
|     ) | ||||
|  | ||||
|     // wait for stream to load | ||||
|     await scene.expectPixelColor(bodyColor, bodyLocation, highTolerance) | ||||
|   }) | ||||
|  | ||||
|   // Test 1: Command bar flow with preselected edges | ||||
|   await test.step(`Select first edge`, async () => { | ||||
|     await scene.expectPixelColor( | ||||
|       edgeColorWhite, | ||||
|       firstEdgeLocation, | ||||
|       lowTolerance | ||||
|     ) | ||||
|     await clickOnFirstEdge() | ||||
|     await scene.expectPixelColor( | ||||
|       edgeColorYellow, | ||||
|       firstEdgeLocation, | ||||
|       highTolerance // Ubuntu color mismatch can require high tolerance | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Apply fillet to the preselected edge`, async () => { | ||||
|     await page.waitForTimeout(100) | ||||
|     await toolbar.filletButton.click() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Fillet', | ||||
|       highlightedHeaderArg: 'selection', | ||||
|       currentArgKey: 'selection', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Selection: '', | ||||
|         Radius: '', | ||||
|       }, | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Fillet', | ||||
|       highlightedHeaderArg: 'radius', | ||||
|       currentArgKey: 'radius', | ||||
|       currentArgValue: '5', | ||||
|       headerArguments: { | ||||
|         Selection: '1 face', | ||||
|         Radius: '', | ||||
|       }, | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Fillet', | ||||
|       headerArguments: { | ||||
|         Selection: '1 face', | ||||
|         Radius: '5', | ||||
|       }, | ||||
|       stage: 'review', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Confirm code is added to the editor`, async () => { | ||||
|     await editor.expectEditor.toContain(firstFilletDeclaration) | ||||
|     await editor.expectState({ | ||||
|       diagnostics: [], | ||||
|       activeLines: ['|>fillet({radius=5,tags=[seg01]},%)'], | ||||
|       highlightedCode: '', | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Confirm scene has changed`, async () => { | ||||
|     await scene.expectPixelColor(filletColor, firstEdgeLocation, lowTolerance) | ||||
|   }) | ||||
|  | ||||
|   // Test 2: Command bar flow without preselected edges | ||||
|   await test.step(`Open fillet UI without selecting edges`, async () => { | ||||
|     await page.waitForTimeout(100) | ||||
|     await toolbar.filletButton.click() | ||||
|     await cmdBar.expectState({ | ||||
|       stage: 'arguments', | ||||
|       currentArgKey: 'selection', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Selection: '', | ||||
|         Radius: '', | ||||
|       }, | ||||
|       highlightedHeaderArg: 'selection', | ||||
|       commandName: 'Fillet', | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Select second edge`, async () => { | ||||
|     await scene.expectPixelColor( | ||||
|       edgeColorWhite, | ||||
|       secondEdgeLocation, | ||||
|       lowTolerance | ||||
|     ) | ||||
|     await clickOnSecondEdge() | ||||
|     await scene.expectPixelColor( | ||||
|       edgeColorYellow, | ||||
|       secondEdgeLocation, | ||||
|       highTolerance // Ubuntu color mismatch can require high tolerance | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Apply fillet to the second edge`, async () => { | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Fillet', | ||||
|       highlightedHeaderArg: 'selection', | ||||
|       currentArgKey: 'selection', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Selection: '', | ||||
|         Radius: '', | ||||
|       }, | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Fillet', | ||||
|       highlightedHeaderArg: 'radius', | ||||
|       currentArgKey: 'radius', | ||||
|       currentArgValue: '5', | ||||
|       headerArguments: { | ||||
|         Selection: '1 sweepEdge', | ||||
|         Radius: '', | ||||
|       }, | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Fillet', | ||||
|       headerArguments: { | ||||
|         Selection: '1 sweepEdge', | ||||
|         Radius: '5', | ||||
|       }, | ||||
|       stage: 'review', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Confirm code is added to the editor`, async () => { | ||||
|     await editor.expectEditor.toContain(secondFilletDeclaration) | ||||
|     await editor.expectState({ | ||||
|       diagnostics: [], | ||||
|       activeLines: ['radius=5,'], | ||||
|       highlightedCode: '', | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Confirm scene has changed`, async () => { | ||||
|     await scene.expectPixelColor( | ||||
|       backgroundColor, | ||||
|       secondEdgeLocation, | ||||
|       lowTolerance | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test(`Chamfer point-and-click`, async ({ | ||||
|   context, | ||||
|   page, | ||||
|   homePage, | ||||
|   scene, | ||||
|   editor, | ||||
|   toolbar, | ||||
|   cmdBar, | ||||
| }) => { | ||||
|   // Code samples | ||||
|   const initialCode = `sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-12, -6], %) | ||||
|   |> line([0, 12], %) | ||||
|   |> line([24, 0], %) | ||||
|   |> line([0, -12], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(-12, sketch001) | ||||
| ` | ||||
|   const firstChamferDeclaration = 'chamfer({ length = 5, tags = [seg01] }, %)' | ||||
|   const secondChamferDeclaration = | ||||
|     'chamfer({       length = 5,       tags = [getOppositeEdge(seg01)]     }, %)' | ||||
|  | ||||
|   // Locators | ||||
|   const firstEdgeLocation = { x: 600, y: 193 } | ||||
|   const secondEdgeLocation = { x: 600, y: 383 } | ||||
|   const bodyLocation = { x: 630, y: 290 } | ||||
|   const [clickOnFirstEdge] = scene.makeMouseHelpers( | ||||
|     firstEdgeLocation.x, | ||||
|     firstEdgeLocation.y | ||||
|   ) | ||||
|   const [clickOnSecondEdge] = scene.makeMouseHelpers( | ||||
|     secondEdgeLocation.x, | ||||
|     secondEdgeLocation.y | ||||
|   ) | ||||
|  | ||||
|   // Colors | ||||
|   const edgeColorWhite: [number, number, number] = [248, 248, 248] | ||||
|   const edgeColorYellow: [number, number, number] = [251, 251, 40] // Mac:B=67 Ubuntu:B=12 | ||||
|   const bodyColor: [number, number, number] = [155, 155, 155] | ||||
|   const chamferColor: [number, number, number] = [168, 168, 168] | ||||
|   const backgroundColor: [number, number, number] = [30, 30, 30] | ||||
|   const lowTolerance = 20 | ||||
|   const highTolerance = 40 | ||||
|  | ||||
|   // Setup | ||||
|   await test.step(`Initial test setup`, async () => { | ||||
|     await context.addInitScript((initialCode) => { | ||||
|       localStorage.setItem('persistCode', initialCode) | ||||
|     }, initialCode) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // verify modeling scene is loaded | ||||
|     await scene.expectPixelColor( | ||||
|       backgroundColor, | ||||
|       secondEdgeLocation, | ||||
|       lowTolerance | ||||
|     ) | ||||
|  | ||||
|     // wait for stream to load | ||||
|     await scene.expectPixelColor(bodyColor, bodyLocation, highTolerance) | ||||
|   }) | ||||
|  | ||||
|   // Test 1: Command bar flow with preselected edges | ||||
|   await test.step(`Select first edge`, async () => { | ||||
|     await scene.expectPixelColor( | ||||
|       edgeColorWhite, | ||||
|       firstEdgeLocation, | ||||
|       lowTolerance | ||||
|     ) | ||||
|     await clickOnFirstEdge() | ||||
|     await scene.expectPixelColor( | ||||
|       edgeColorYellow, | ||||
|       firstEdgeLocation, | ||||
|       highTolerance // Ubuntu color mismatch can require high tolerance | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Apply chamfer to the preselected edge`, async () => { | ||||
|     await page.waitForTimeout(100) | ||||
|     await toolbar.chamferButton.click() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Chamfer', | ||||
|       highlightedHeaderArg: 'selection', | ||||
|       currentArgKey: 'selection', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Selection: '', | ||||
|         Length: '', | ||||
|       }, | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Chamfer', | ||||
|       highlightedHeaderArg: 'length', | ||||
|       currentArgKey: 'length', | ||||
|       currentArgValue: '5', | ||||
|       headerArguments: { | ||||
|         Selection: '1 face', | ||||
|         Length: '', | ||||
|       }, | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Chamfer', | ||||
|       headerArguments: { | ||||
|         Selection: '1 face', | ||||
|         Length: '5', | ||||
|       }, | ||||
|       stage: 'review', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Confirm code is added to the editor`, async () => { | ||||
|     await editor.expectEditor.toContain(firstChamferDeclaration) | ||||
|     await editor.expectState({ | ||||
|       diagnostics: [], | ||||
|       activeLines: ['|>chamfer({length=5,tags=[seg01]},%)'], | ||||
|       highlightedCode: '', | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Confirm scene has changed`, async () => { | ||||
|     await scene.expectPixelColor(chamferColor, firstEdgeLocation, lowTolerance) | ||||
|   }) | ||||
|  | ||||
|   // Test 2: Command bar flow without preselected edges | ||||
|   await test.step(`Open chamfer UI without selecting edges`, async () => { | ||||
|     await page.waitForTimeout(100) | ||||
|     await toolbar.chamferButton.click() | ||||
|     await cmdBar.expectState({ | ||||
|       stage: 'arguments', | ||||
|       currentArgKey: 'selection', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Selection: '', | ||||
|         Length: '', | ||||
|       }, | ||||
|       highlightedHeaderArg: 'selection', | ||||
|       commandName: 'Chamfer', | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Select second edge`, async () => { | ||||
|     await scene.expectPixelColor( | ||||
|       edgeColorWhite, | ||||
|       secondEdgeLocation, | ||||
|       lowTolerance | ||||
|     ) | ||||
|     await clickOnSecondEdge() | ||||
|     await scene.expectPixelColor( | ||||
|       edgeColorYellow, | ||||
|       secondEdgeLocation, | ||||
|       highTolerance // Ubuntu color mismatch can require high tolerance | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Apply chamfer to the second edge`, async () => { | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Chamfer', | ||||
|       highlightedHeaderArg: 'selection', | ||||
|       currentArgKey: 'selection', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Selection: '', | ||||
|         Length: '', | ||||
|       }, | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Chamfer', | ||||
|       highlightedHeaderArg: 'length', | ||||
|       currentArgKey: 'length', | ||||
|       currentArgValue: '5', | ||||
|       headerArguments: { | ||||
|         Selection: '1 sweepEdge', | ||||
|         Length: '', | ||||
|       }, | ||||
|       stage: 'arguments', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await cmdBar.expectState({ | ||||
|       commandName: 'Chamfer', | ||||
|       headerArguments: { | ||||
|         Selection: '1 sweepEdge', | ||||
|         Length: '5', | ||||
|       }, | ||||
|       stage: 'review', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Confirm code is added to the editor`, async () => { | ||||
|     await editor.expectEditor.toContain(secondChamferDeclaration) | ||||
|     await editor.expectState({ | ||||
|       diagnostics: [], | ||||
|       activeLines: ['length=5,'], | ||||
|       highlightedCode: '', | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Confirm scene has changed`, async () => { | ||||
|     await scene.expectPixelColor( | ||||
|       backgroundColor, | ||||
|       secondEdgeLocation, | ||||
|       lowTolerance | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| @ -907,6 +1566,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => { | ||||
|         await clickOnCap() | ||||
|         await page.waitForTimeout(500) | ||||
|         await cmdBar.progressCmdBar() | ||||
|         await page.waitForTimeout(500) | ||||
|         await cmdBar.progressCmdBar() | ||||
|         await cmdBar.expectState({ | ||||
|           stage: 'review', | ||||
| @ -927,6 +1587,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => { | ||||
|       await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => { | ||||
|         await toolbar.shellButton.click() | ||||
|         await cmdBar.progressCmdBar() | ||||
|         await page.waitForTimeout(500) | ||||
|         await cmdBar.progressCmdBar() | ||||
|         await cmdBar.expectState({ | ||||
|           stage: 'review', | ||||
| @ -1008,6 +1669,7 @@ extrude001 = extrude(40, sketch001) | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.up('Shift') | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await page.waitForTimeout(500) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await cmdBar.expectState({ | ||||
|       stage: 'review', | ||||
| @ -1030,4 +1692,162 @@ extrude001 = extrude(40, sketch001) | ||||
|     }) | ||||
|     await scene.expectPixelColor([49, 49, 49], testPoint, 15) | ||||
|   }) | ||||
|  | ||||
|   await test.step('Delete shell via feature tree selection', async () => { | ||||
|     await editor.closePane() | ||||
|     const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0) | ||||
|     await operationButton.click({ button: 'left' }) | ||||
|     await page.keyboard.press('Backspace') | ||||
|     await scene.expectPixelColor([99, 99, 99], testPoint, 15) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| const shellSketchOnFacesCases = [ | ||||
|   `sketch001 = startSketchOn('XZ') | ||||
|   |> circle({ center = [0, 0], radius = 100 }, %) | ||||
|   |> extrude(100, %) | ||||
|  | ||||
| sketch002 = startSketchOn(sketch001, 'END') | ||||
|   |> circle({ center = [0, 0], radius = 50 }, %) | ||||
|   |> extrude(50, %) | ||||
|   `, | ||||
|   `sketch001 = startSketchOn('XZ') | ||||
|   |> circle({ center = [0, 0], radius = 100 }, %) | ||||
| extrude001 = extrude(100, sketch001) | ||||
|  | ||||
| sketch002 = startSketchOn(extrude001, 'END') | ||||
|   |> circle({ center = [0, 0], radius = 50 }, %) | ||||
| extrude002 = extrude(50, sketch002) | ||||
|   `, | ||||
| ] | ||||
| shellSketchOnFacesCases.forEach((initialCode, index) => { | ||||
|   const hasExtrudesInPipe = index === 0 | ||||
|   test(`Shell point-and-click sketch on face (extrudes in pipes: ${hasExtrudesInPipe})`, async ({ | ||||
|     context, | ||||
|     page, | ||||
|     homePage, | ||||
|     scene, | ||||
|     editor, | ||||
|     toolbar, | ||||
|     cmdBar, | ||||
|   }) => { | ||||
|     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: 550, y: 295 } | ||||
|     const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||
|     const shellDeclaration = `shell001 = shell({ faces = ['end'], thickness = 5 }, ${ | ||||
|       hasExtrudesInPipe ? 'sketch002' : 'extrude002' | ||||
|     })` | ||||
|  | ||||
|     await test.step(`Look for the grey of the shape`, async () => { | ||||
|       await toolbar.closePane('code') | ||||
|       await scene.expectPixelColor([128, 128, 128], testPoint, 15) | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Go through the command bar flow, selecting a cap and keeping default thickness`, async () => { | ||||
|       await toolbar.shellButton.click() | ||||
|       await cmdBar.expectState({ | ||||
|         stage: 'arguments', | ||||
|         currentArgKey: 'selection', | ||||
|         currentArgValue: '', | ||||
|         headerArguments: { | ||||
|           Selection: '', | ||||
|           Thickness: '', | ||||
|         }, | ||||
|         highlightedHeaderArg: 'selection', | ||||
|         commandName: 'Shell', | ||||
|       }) | ||||
|       await clickOnCap() | ||||
|       await page.waitForTimeout(500) | ||||
|       await cmdBar.progressCmdBar() | ||||
|       await page.waitForTimeout(500) | ||||
|       await cmdBar.progressCmdBar() | ||||
|       await page.waitForTimeout(500) | ||||
|       await cmdBar.expectState({ | ||||
|         stage: 'review', | ||||
|         headerArguments: { | ||||
|           Selection: '1 cap', | ||||
|           Thickness: '5', | ||||
|         }, | ||||
|         commandName: 'Shell', | ||||
|       }) | ||||
|       await cmdBar.progressCmdBar() | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Confirm code is added to the editor, scene has changed`, async () => { | ||||
|       await toolbar.openPane('code') | ||||
|       await editor.expectEditor.toContain(shellDeclaration) | ||||
|       await editor.expectState({ | ||||
|         diagnostics: [], | ||||
|         activeLines: [shellDeclaration], | ||||
|         highlightedCode: '', | ||||
|       }) | ||||
|       await toolbar.closePane('code') | ||||
|       await scene.expectPixelColor([73, 73, 73], testPoint, 15) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test(`Shell dry-run validation rejects sweeps`, async ({ | ||||
|   context, | ||||
|   page, | ||||
|   homePage, | ||||
|   scene, | ||||
|   editor, | ||||
|   toolbar, | ||||
|   cmdBar, | ||||
| }) => { | ||||
|   const initialCode = `sketch001 = startSketchOn('YZ') | ||||
|   |> circle({ | ||||
|        center = [0, 0], | ||||
|        radius = 500 | ||||
|      }, %) | ||||
| sketch002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> xLine(-2000, %) | ||||
| sweep001 = sweep({ path = sketch002 }, sketch001) | ||||
| ` | ||||
|   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: 500, y: 250 } | ||||
|   const [clickOnSweep] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||
|  | ||||
|   await test.step(`Confirm sweep exists`, async () => { | ||||
|     await toolbar.closePane('code') | ||||
|     await scene.expectPixelColor([231, 231, 231], testPoint, 15) | ||||
|   }) | ||||
|  | ||||
|   await test.step(`Go through the Shell flow and fail validation with a toast`, async () => { | ||||
|     await toolbar.shellButton.click() | ||||
|     await cmdBar.expectState({ | ||||
|       stage: 'arguments', | ||||
|       currentArgKey: 'selection', | ||||
|       currentArgValue: '', | ||||
|       headerArguments: { | ||||
|         Selection: '', | ||||
|         Thickness: '', | ||||
|       }, | ||||
|       highlightedHeaderArg: 'selection', | ||||
|       commandName: 'Shell', | ||||
|     }) | ||||
|     await clickOnSweep() | ||||
|     await page.waitForTimeout(500) | ||||
|     await cmdBar.progressCmdBar() | ||||
|     await expect( | ||||
|       page.getByText('Unable to shell with the provided selection') | ||||
|     ).toBeVisible() | ||||
|     await page.waitForTimeout(1000) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| @ -115,7 +115,7 @@ test( | ||||
| ) | ||||
|  | ||||
| test( | ||||
|   'yyyyyyyyy open a file in a project works and renders, open another file in different project with errors, it should clear the scene', | ||||
|   'open a file in a project works and renders, open another file in different project with errors, it should clear the scene', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ context, page }, testInfo) => { | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
| @ -172,7 +172,7 @@ test( | ||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||
|       await expect(page.getByText('broken-code')).toBeVisible() | ||||
|       await expect(page.getByText('bracket')).toBeVisible() | ||||
|       await expect(page.getByText('New Project')).toBeVisible() | ||||
|       await expect(page.getByText('Create project')).toBeVisible() | ||||
|     }) | ||||
|     await test.step('opening broken code project should clear the scene and show the error', async () => { | ||||
|       // Go back home. | ||||
| @ -199,7 +199,7 @@ test( | ||||
| ) | ||||
|  | ||||
| test( | ||||
|   'aaayyyyyyyy open a file in a project works and renders, open another file in different project that is empty, it should clear the scene', | ||||
|   'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ context, page }, testInfo) => { | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
| @ -253,7 +253,7 @@ test( | ||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||
|       await expect(page.getByText('empty')).toBeVisible() | ||||
|       await expect(page.getByText('bracket')).toBeVisible() | ||||
|       await expect(page.getByText('New Project')).toBeVisible() | ||||
|       await expect(page.getByText('Create project')).toBeVisible() | ||||
|     }) | ||||
|     await test.step('opening empty code project should clear the scene', async () => { | ||||
|       // Go back home. | ||||
| @ -276,7 +276,7 @@ test( | ||||
| ) | ||||
|  | ||||
| test( | ||||
|   'nooooooooooooo open a file in a project works and renders, open empty file, it should clear the scene', | ||||
|   'open a file in a project works and renders, open empty file, it should clear the scene', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ context, page }, testInfo) => { | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
| @ -985,6 +985,126 @@ test.describe(`Project management commands`, () => { | ||||
|       }) | ||||
|     } | ||||
|   ) | ||||
|   test(`Create a new project with a colliding name`, async ({ | ||||
|     context, | ||||
|     homePage, | ||||
|     toolbar, | ||||
|     cmdBar, | ||||
|   }) => { | ||||
|     const projectName = 'test-project' | ||||
|     await test.step(`Setup`, async () => { | ||||
|       await context.folderSetupFn(async (dir) => { | ||||
|         const projectDir = path.join(dir, projectName) | ||||
|         await Promise.all([fsp.mkdir(projectDir, { recursive: true })]) | ||||
|         await Promise.all([ | ||||
|           fsp.copyFile( | ||||
|             executorInputPath('router-template-slate.kcl'), | ||||
|             path.join(projectDir, 'main.kcl') | ||||
|           ), | ||||
|         ]) | ||||
|       }) | ||||
|       await homePage.expectState({ | ||||
|         projectCards: [ | ||||
|           { | ||||
|             title: projectName, | ||||
|             fileCount: 1, | ||||
|           }, | ||||
|         ], | ||||
|         sortBy: 'last-modified-desc', | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     await test.step('Create a new project with the same name', async () => { | ||||
|       await cmdBar.openCmdBar() | ||||
|       await cmdBar.chooseCommand('create project') | ||||
|       await cmdBar.expectState({ | ||||
|         stage: 'arguments', | ||||
|         commandName: 'Create project', | ||||
|         currentArgKey: 'name', | ||||
|         currentArgValue: '', | ||||
|         headerArguments: { | ||||
|           Name: '', | ||||
|         }, | ||||
|         highlightedHeaderArg: 'name', | ||||
|       }) | ||||
|       await cmdBar.argumentInput.fill(projectName) | ||||
|       await cmdBar.progressCmdBar() | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Check the project was created with a non-colliding name`, async () => { | ||||
|       await toolbar.logoLink.click() | ||||
|       await homePage.expectState({ | ||||
|         projectCards: [ | ||||
|           { | ||||
|             title: projectName + '-1', | ||||
|             fileCount: 1, | ||||
|           }, | ||||
|           { | ||||
|             title: projectName, | ||||
|             fileCount: 1, | ||||
|           }, | ||||
|         ], | ||||
|         sortBy: 'last-modified-desc', | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     await test.step('Create another project with the same name', async () => { | ||||
|       await cmdBar.openCmdBar() | ||||
|       await cmdBar.chooseCommand('create project') | ||||
|       await cmdBar.expectState({ | ||||
|         stage: 'arguments', | ||||
|         commandName: 'Create project', | ||||
|         currentArgKey: 'name', | ||||
|         currentArgValue: '', | ||||
|         headerArguments: { | ||||
|           Name: '', | ||||
|         }, | ||||
|         highlightedHeaderArg: 'name', | ||||
|       }) | ||||
|       await cmdBar.argumentInput.fill(projectName) | ||||
|       await cmdBar.progressCmdBar() | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Check the second project was created with a non-colliding name`, async () => { | ||||
|       await toolbar.logoLink.click() | ||||
|       await homePage.expectState({ | ||||
|         projectCards: [ | ||||
|           { | ||||
|             title: projectName + '-2', | ||||
|             fileCount: 1, | ||||
|           }, | ||||
|           { | ||||
|             title: projectName + '-1', | ||||
|             fileCount: 1, | ||||
|           }, | ||||
|           { | ||||
|             title: projectName, | ||||
|             fileCount: 1, | ||||
|           }, | ||||
|         ], | ||||
|         sortBy: 'last-modified-desc', | ||||
|       }) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test(`Create a few projects using the default project name`, async ({ | ||||
|   homePage, | ||||
|   toolbar, | ||||
| }) => { | ||||
|   for (let i = 0; i < 12; i++) { | ||||
|     await test.step(`Create project ${i}`, async () => { | ||||
|       await homePage.expectState({ | ||||
|         projectCards: Array.from({ length: i }, (_, i) => ({ | ||||
|           title: `project-${i.toString().padStart(3, '0')}`, | ||||
|           fileCount: 1, | ||||
|         })).toReversed(), | ||||
|         sortBy: 'last-modified-desc', | ||||
|       }) | ||||
|       await homePage.createAndGoToProject() | ||||
|       await toolbar.logoLink.click() | ||||
|     }) | ||||
|   } | ||||
| }) | ||||
|  | ||||
| test( | ||||
| @ -1391,7 +1511,7 @@ extrude001 = extrude(200, sketch001)`) | ||||
|     await page.getByTestId('app-logo').click() | ||||
|  | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'New project' }) | ||||
|       page.getByRole('button', { name: 'Create project' }) | ||||
|     ).toBeVisible() | ||||
|  | ||||
|     for (let i = 1; i <= 10; i++) { | ||||
| @ -1465,7 +1585,7 @@ test( | ||||
|  | ||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||
|       await expect(page.getByText('router-template-slate')).toBeVisible() | ||||
|       await expect(page.getByText('New Project')).toBeVisible() | ||||
|       await expect(page.getByText('Create project')).toBeVisible() | ||||
|     }) | ||||
|  | ||||
|     await test.step('Opening the router-template project should load the stream', async () => { | ||||
| @ -1494,7 +1614,7 @@ test( | ||||
|  | ||||
|       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||
|       await expect(page.getByText('router-template-slate')).toBeVisible() | ||||
|       await expect(page.getByText('New Project')).toBeVisible() | ||||
|       await expect(page.getByText('Create project')).toBeVisible() | ||||
|     }) | ||||
|   } | ||||
| ) | ||||
| @ -1885,3 +2005,48 @@ test.fixme( | ||||
|     }) | ||||
|   } | ||||
| ) | ||||
|  | ||||
| test( | ||||
|   'project name with foreign characters should open', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ context, page }, testInfo) => { | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
|       const bracketDir = path.join(dir, 'اَلْعَرَبِيَّةُ') | ||||
|       await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|       await fsp.copyFile( | ||||
|         executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|         path.join(bracketDir, 'main.kcl') | ||||
|       ) | ||||
|  | ||||
|       await fsp.writeFile(path.join(bracketDir, 'empty.kcl'), '') | ||||
|     }) | ||||
|  | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     page.on('console', console.log) | ||||
|  | ||||
|     const pointOnModel = { x: 630, y: 280 } | ||||
|  | ||||
|     await test.step('Opening the اَلْعَرَبِيَّةُ project should load the stream', async () => { | ||||
|       // expect to see the text bracket | ||||
|       await expect(page.getByText('اَلْعَرَبِيَّةُ')).toBeVisible() | ||||
|  | ||||
|       await page.getByText('اَلْعَرَبِيَّةُ').click() | ||||
|  | ||||
|       await expect( | ||||
|         page.getByRole('button', { name: 'Start Sketch' }) | ||||
|       ).toBeEnabled({ | ||||
|         timeout: 20_000, | ||||
|       }) | ||||
|  | ||||
|       // 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]), { | ||||
|           timeout: 10_000, | ||||
|         }) | ||||
|         .toBeLessThan(15) | ||||
|     }) | ||||
|   } | ||||
| ) | ||||
|  | ||||
| @ -614,6 +614,38 @@ extrude001 = extrude(50, sketch001) | ||||
|       await expect(gizmo).toBeVisible() | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   test(`Refreshing the app doesn't cause the stream to pause on long-executing files`, async ({ | ||||
|     context, | ||||
|     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'), | ||||
|         path.join(legoDir, 'main.kcl') | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Test setup`, async () => { | ||||
|       await homePage.openProject('lego') | ||||
|       await toolbar.closePane('code') | ||||
|     }) | ||||
|     await test.step(`Waiting for the loading spinner to disappear`, async () => { | ||||
|       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 | ||||
|       ) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| async function clickExportButton(page: Page) { | ||||
|  | ||||
| @ -39,8 +39,8 @@ test.describe('Sketch tests', () => { | ||||
|   ${startProfileAt1} | ||||
|   |> arc({ | ||||
|         radius = screwRadius, | ||||
|         angle_start = 0, | ||||
|         angle_end = 360 | ||||
|         angleStart = 0, | ||||
|         angleEnd = 360 | ||||
|       }, %) | ||||
|    | ||||
|     part001 = startSketchOn('XY') | ||||
| @ -60,8 +60,8 @@ test.describe('Sketch tests', () => { | ||||
|   |> yLine(wireOffset, %) | ||||
|   |> arc({ | ||||
|         radius = wireRadius, | ||||
|         angle_start = 0, | ||||
|         angle_end = 180 | ||||
|         angleStart = 0, | ||||
|         angleEnd = 180 | ||||
|       }, %) | ||||
|   |> yLine(-wireOffset, %) | ||||
|   |> xLine(-width / 4, %) | ||||
| @ -1323,3 +1323,85 @@ test.describe(`Sketching with offset planes`, () => { | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| // Regression test for https://github.com/KittyCAD/modeling-app/issues/4891 | ||||
| test.describe(`Click based selection don't brick the app when clicked out of range after format using cache`, () => { | ||||
|   test(`Can select a line that reformmed after entering sketch mode`, async ({ | ||||
|     context, | ||||
|     page, | ||||
|     scene, | ||||
|     toolbar, | ||||
|     editor, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     // We seed the scene with a single offset plane | ||||
|     await context.addInitScript(() => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> line([3.14, 3.14], %) | ||||
|   |> arcTo({ | ||||
|   end = [4, 2], | ||||
|   interior = [1, 2] | ||||
|   }, %) | ||||
| ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|     await scene.waitForExecutionDone() | ||||
|  | ||||
|     await test.step(`format the code`, async () => { | ||||
|       // doesn't contain condensed version | ||||
|       await editor.expectEditor.not.toContain( | ||||
|         `arcTo({ end = [4, 2], interior = [1, 2] }, %)` | ||||
|       ) | ||||
|       // click the code to enter sketch mode | ||||
|       await page.getByText(`arcTo`).click() | ||||
|       // Format the code. | ||||
|       await page.locator('#code-pane button:first-child').click() | ||||
|       await page.locator('button:has-text("Format code")').click() | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Ensure the code reformatted`, async () => { | ||||
|       await editor.expectEditor.toContain( | ||||
|         `arcTo({ end = [4, 2], interior = [1, 2] }, %)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const [arcClick, arcHover] = scene.makeMouseHelpers(699, 337) | ||||
|     await test.step('Ensure we can hover the arc', async () => { | ||||
|       await arcHover() | ||||
|  | ||||
|       // Check that the code is highlighted | ||||
|       await editor.expectState({ | ||||
|         activeLines: ["sketch001=startSketchOn('XZ')"], | ||||
|         diagnostics: [], | ||||
|         highlightedCode: 'arcTo({end = [4, 2], interior = [1, 2]}, %)', | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     await test.step('reset the selection', async () => { | ||||
|       // Move the mouse out of the way | ||||
|       await page.mouse.move(655, 337) | ||||
|  | ||||
|       await editor.expectState({ | ||||
|         activeLines: ["sketch001=startSketchOn('XZ')"], | ||||
|         diagnostics: [], | ||||
|         highlightedCode: '', | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     await test.step('Ensure we can click the arc', async () => { | ||||
|       await arcClick() | ||||
|  | ||||
|       // Check that the code is highlighted | ||||
|       await editor.expectState({ | ||||
|         activeLines: [], | ||||
|         diagnostics: [], | ||||
|         highlightedCode: 'arcTo({end = [4, 2], interior = [1, 2]}, %)', | ||||
|       }) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB | 
| Before Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB | 
| Before Width: | Height: | Size: 55 KiB | 
| Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB | 
| Before Width: | Height: | Size: 48 KiB | 
| Before Width: | Height: | Size: 43 KiB | 
| Before Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 41 KiB | 
| Before Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 35 KiB | 
| Before Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 57 KiB | 
| Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 42 KiB | 
| Before Width: | Height: | Size: 54 KiB | 
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB | 
| Before Width: | Height: | Size: 65 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB | 
| Before Width: | Height: | Size: 62 KiB | 
| Before Width: | Height: | Size: 152 KiB | 
| Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 145 KiB | 
| Before Width: | Height: | Size: 130 KiB | 
| Before Width: | Height: | Size: 136 KiB | 
| Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 129 KiB | 
| Before Width: | Height: | Size: 112 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 48 KiB | 
| Before Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 54 KiB | 
| Before Width: | Height: | Size: 41 KiB | 
| Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 48 KiB | 
| Before Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 53 KiB | 
| Before Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 53 KiB | 
| Before Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 38 KiB | 
| @ -1078,7 +1078,7 @@ export async function createProject({ | ||||
|   returnHome?: boolean | ||||
| }) { | ||||
|   await test.step(`Create project and navigate to it`, async () => { | ||||
|     await page.getByRole('button', { name: 'New project' }).click() | ||||
|     await page.getByRole('button', { name: 'Create project' }).click() | ||||
|     await page.getByRole('textbox', { name: 'Name' }).fill(name) | ||||
|     await page.getByRole('button', { name: 'Continue' }).click() | ||||
|  | ||||
|  | ||||
