Compare commits

...

34 Commits

Author SHA1 Message Date
6e03cf61f4 Fix kcl singleton 2025-03-13 15:07:38 -04:00
2a24a4da82 Fix to return it 2025-03-13 15:07:38 -04:00
3e186fbe6b Change mock IDs to be stable 2025-03-13 15:07:38 -04:00
0372f35ce1 Fix spelling 2025-03-13 15:07:38 -04:00
219bc4c8a3 Add comment about mock engine 2025-03-13 15:07:38 -04:00
0130d19cfb Update output since rebase 2025-03-13 15:07:38 -04:00
a96a5fcba8 Add updating the artifact graph after mock execution 2025-03-13 15:07:38 -04:00
79374a3dc0 Update output after not requiring responses 2025-03-13 15:07:38 -04:00
9563fb9a5e Add building the artifact graph in sketch mode 2025-03-13 15:07:38 -04:00
6fb32eeff2 fix python bindings building on linux (#5795)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-13 12:05:45 -07:00
ec64daa01f try codspeed runner (#5796)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-13 12:05:15 -07:00
e8886bb358 hide program memory source ranges from the app (#5759)
* hide program memory source ranges from the app

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix unit tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix memory

Signed-off-by: Jess Frazelle <github@jessfraz.com>

remove from paths

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix program memory source ranges

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2025-03-13 11:13:33 -07:00
05a6313d97 try execute bench with walltime (#5772)
* try execute bench

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* timeout longer

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* timeout longer

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-13 10:33:00 -07:00
80f78e1c61 Revolve/Sweep multiple sketches at once (#5779)
* revolve multiple sketches at once

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* do the same for swweep

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* do the same for swweep

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-13 09:38:22 -07:00
c441a3ab1c Fix playwright mental model, don't make me psycho, thanks (#5680)
* Fix flakey tests with new toolbar.exitSketch

* tsc && lint && fmt

* Disable pw electron thing again

* Unfrig Playwright-Electron a ton; fix another ton of flakes.

* More deflaky

* Fix a ton of tests and playwright related hell

* Run jess's magic incantation to build rust kcl things

* yarn tsc

* yarn lint

* yarn fmt

* Remove double logs

* Revert to old settings spreads momentarily

* Expect error *in the fixtureSetup*, does not circumvent typechecking for regular usage

* Fix unit tests
2025-03-13 10:54:00 -04:00
e894242768 Add warning when using a module with no return value (#5771)
* Add warning when using a module with no return value

* Update output files since changing source range of the pipeline argument

* Change wording of error message to not use the term unlabeled
2025-03-12 18:05:18 +00:00
d8dff03746 Add cache for wasm in build-apps (#5758)
* WIP: break out e2e snapshots

* Separate prepare-wasm job

* Force e2e to run on pierremtb/adhoc/break-out-snapshots

* Quick fix

* Quick fix2

* Quick fix3

* Quick fix4

* Quick fix5

* Remove ifs for flow tests

* Clean up for review

* Cache wasm on PR for build-apps

* Add checks on build wasm tings

* Clean up for review
2025-03-12 13:53:30 -04:00
60aee7ddba Bump the security group with 2 updates (#5773)
Bumps the security group with 2 updates: [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) and [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime).


Updates `@babel/helpers` from 7.25.0 to 7.26.10
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-helpers)

Updates `@babel/runtime` from 7.25.0 to 7.26.10
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/helpers"
  dependency-type: indirect
  dependency-group: security
- dependency-name: "@babel/runtime"
  dependency-type: indirect
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 10:03:14 -07:00
6c09da24a4 change server path for vscode extension (#5769)
change server path

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-12 16:46:18 +00:00
b61cd3123f KCL: Fix two cryptic error messages (#5745)
Firstly, before the code "{x = 1, y = }" would give the dreaded "unexpected token" error.

Now it says "This property has a label, but no value. Put some value after the equals sign".
And points to the = symbol with no matching right-hand side value. Yay!

Second fix: before, in the code `f(1, x=)`, the error complained that an unlabeled arg was not permitted there.

Now it says "This argument has a label, but no value. Put some value after the equals sign".
2025-03-12 16:27:10 +00:00
865bf8ae7a KCL: Fix cryptic error when using duplicate edges in fillet call (#5755)
Fixes https://github.com/KittyCAD/modeling-app/issues/4307

Now if you try to fillet the same edge twice in a single fillet command,
the error message is clearer, and the source range will highlight
the specific edges in the array which are duplicated.

Same goes for chamfer.

Note: although the Rust KCL interpreter sends back an array of SourceRange
for each KCL error, the frontend only puts the first one into CodeMirror
diagnostics. We should fix that: https://github.com/KittyCAD/modeling-app/issues/5754
2025-03-12 16:24:27 +00:00
f8e53c6577 Fix web onboarding dismissal infinite loop bug (#5742) 2025-03-12 13:34:11 +00:00
f31c2c6f81 Import geometry work w transforms (#5757)
* make work with imported geometry

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* iupdates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* update known issues

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-12 01:23:21 +00:00
e5c05e1980 change test names kcl-samples (#5766)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-11 23:29:26 +00:00
6d0da100e5 benchmark retry (#5760)
* benchmark retry

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix times

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix times

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* dont use a custom iter

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* just parse

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* use runs on

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-11 22:21:28 +00:00
b8a0ad7144 Dedicated job for snapshot tests (#5752)
* WIP: break out e2e snapshots

* Separate prepare-wasm job

* Force e2e to run on pierremtb/adhoc/break-out-snapshots

* Quick fix

* Quick fix2

* Quick fix3

* Quick fix4

* Quick fix5

* Remove ifs for flow tests

* Clean up for review

* should-run == 'true' madness
2025-03-11 17:18:42 -04:00
724e65ac97 Hide selection-type args from command palette while in an edit flow (#5763)
* Allow `hidden` config to be a callback

* Hide selection type args if `nodeToEdit` is present

in workflows that have an edit flow set up

* Remove Selection from headerArguments in edit flow tests
2025-03-11 16:29:03 -04:00
b5028f7aa8 Change to not expose exec artifacts (#5700)
Now that the artifact graph is built completely in Rust, these are an
implementation detail that shouldn't be exposed to TS.
2025-03-11 19:09:11 +00:00
df6b4f4c37 Move consts to dir in docs (#5753)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add consts to dir

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add consts to dir

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-11 18:44:27 +00:00
41eb64925b Improve sketch constraint test (#5756) 2025-03-11 14:14:03 -04:00
fc076173ff Make any error show on ErrorPage, not just route error responses (#5623)
* Make any error show on ErrorPage, not just route error responses

Closes #5620 by making any error show on the page so the user can copy
it for use in an issue. Previously, the logic was too narrow and only
showed the error on the page if `isRouterErrorResponse` was true.

* @nadr0's feedback

thanks for giving my crap work a review

* Update src/components/ErrorPage.tsx

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

* @nadr0 feedback, handle overflowing error case

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
2025-03-11 17:46:07 +00:00
98822869f7 fix settings docs names (#5751)
* fix settings docs names

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* add consts to dir

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-11 10:20:18 -07:00
df0510c199 cleandocs (#5750)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-11 08:51:23 -07:00
fda65bcbd7 codspeed (#5741)
* codspeed

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Update cargo-bench.yml

* Update cargo-bench.yml

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Update settings.md (#5743)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-11 08:03:49 -07:00
428 changed files with 5762460 additions and 319575 deletions

View File

@ -33,26 +33,63 @@ jobs:
- run: yarn install - run: yarn install
- id: filter
name: Check for Rust changes
uses: dorny/paths-filter@v3
with:
filters: |
rust:
- 'rust/**'
- name: Download Wasm Cache
id: download-wasm
if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: rust/kcl-wasm-lib/pkg
- name: Build WASM condition
id: wasm
run: |
set -euox pipefail
# Build wasm if this is a push to main or tag, there are Rust changes, or
# downloading from the wasm cache failed.
if [[ ${{github.event_name}} == 'push' || ${{steps.filter.outputs.rust}} == 'true' || ${{steps.download-wasm.outcome}} == 'failure' ]]; then
echo "should-build-wasm=true" >> $GITHUB_OUTPUT
else
echo "should-build-wasm=false" >> $GITHUB_OUTPUT
fi
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash shell: bash
run: | run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ [ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust - name: Install rust
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
cache: false # Configured below. cache: false # Configured below.
# TODO: see if we can fetch from main instead if no diff at rust
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3 - uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
with: with:
tool: wasm-pack tool: wasm-pack
- name: Rust Cache - name: Rust Cache
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
workspaces: rust workspaces: rust
- name: Run build:wasm - name: Run build:wasm
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
run: "yarn build:wasm" run: "yarn build:wasm"
- name: Set nightly version, product name, release notes, and icons - name: Set nightly version, product name, release notes, and icons

View File

@ -26,7 +26,7 @@ name: cargo bench
jobs: jobs:
cargo-bench: cargo-bench:
name: cargo bench name: cargo bench
runs-on: ubuntu-latest-8-cores runs-on: codspeed-macro
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
@ -40,13 +40,19 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
cargo install cargo-criterion cargo install cargo-criterion
sudo apt update cargo install cargo-codspeed
sudo apt install -y valgrind cd rust/kcl-lib
- uses: boa-dev/criterion-compare-action@v3 cargo add --dev codspeed-criterion-compat --rename criterion
- name: Build the benchmark target(s)
run: |
cd rust
cargo codspeed build --measurement-mode walltime
- name: Run the benchmarks
uses: CodSpeedHQ/action@v3
with: with:
cwd: "rust" working-directory: rust
defaultFeatures: true run: cargo codspeed run
# Needed. The name of the branch to compare with. This default uses the branch which is being pulled against token: ${{ secrets.CODSPEED_TOKEN }}
branchName: ${{ github.base_ref }} mode: walltime
env: env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN }}

View File

@ -26,9 +26,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Fetch the base branch - name: Fetch the base branch
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
run: git fetch origin ${{ github.base_ref }} --depth=1 run: git fetch origin ${{ github.base_ref }} --depth=1
- name: Check for path changes - name: Check for path changes
id: path-changes id: path-changes
shell: bash shell: bash
@ -48,6 +50,7 @@ jobs:
else else
echo "significant=false" >> $GITHUB_OUTPUT echo "significant=false" >> $GITHUB_OUTPUT
fi fi
- name: Should run - name: Should run
id: should-run id: should-run
shell: bash shell: bash
@ -60,6 +63,7 @@ jobs:
else else
echo "should-run=false" >> $GITHUB_OUTPUT echo "should-run=false" >> $GITHUB_OUTPUT
fi fi
- name: Display conditions - name: Display conditions
shell: bash shell: bash
run: | run: |
@ -68,7 +72,216 @@ jobs:
echo "significant: ${{ steps.path-changes.outputs.significant }}" echo "significant: ${{ steps.path-changes.outputs.significant }}"
echo "should-run: ${{ steps.should-run.outputs.should-run }}" echo "should-run: ${{ steps.should-run.outputs.should-run }}"
prepare-wasm:
# seperate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
runs-on: namespace-profile-ubuntu-8-cores
needs: conditions
steps:
- uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
- id: filter
if: needs.conditions.outputs.should-run == 'true'
name: Check for Rust changes
uses: dorny/paths-filter@v3
with:
filters: |
rust:
- 'rust/**'
- uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
if: needs.conditions.outputs.should-run == 'true'
run: yarn
- name: Download Wasm Cache
id: download-wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: rust/kcl-wasm-lib/pkg
- name: Build WASM condition
id: wasm
if: needs.conditions.outputs.should-run == 'true'
run: |
set -euox pipefail
# Build wasm if this is a scheduled run, there are Rust changes, or
# downloading from the wasm cache failed.
if [[ ${{github.event_name}} == 'schedule' || ${{steps.filter.outputs.rust}} == 'true' || ${{steps.download-wasm.outcome}} == 'failure' ]]; then
echo "should-build-wasm=true" >> $GITHUB_OUTPUT
else
echo "should-build-wasm=false" >> $GITHUB_OUTPUT
fi
- name: Use correct Rust toolchain
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
with:
tool: wasm-pack
- name: Rust Cache
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: Build Wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: yarn build:wasm
- uses: actions/upload-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
with:
name: prepared-wasm
path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
snapshots:
name: playwright:snapshots:ubuntu
runs-on: namespace-profile-ubuntu-8-cores
needs: [conditions, prepare-wasm]
steps:
- uses: actions/create-github-app-token@v1
if: needs.conditions.outputs.should-run == 'true'
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
with:
token: ${{ steps.app-token.outputs.token }}
- uses: actions/download-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
name: prepared-wasm
- name: Copy prepared wasm
if: needs.conditions.outputs.should-run == 'true'
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
id: deps-install
if: needs.conditions.outputs.should-run == 'true'
run: yarn
- name: Cache Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
- name: Install Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
run: yarn playwright install --with-deps
- name: build web
if: needs.conditions.outputs.should-run == 'true'
run: yarn tronb:vite:dev
- name: Run ubuntu/chrome snapshots
if: needs.conditions.outputs.should-run == 'true'
run: |
yarn test:snapshots
env:
CI: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
with:
name: playwright-report-snapshots-${{ matrix.os }}-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true
- name: Clean up test-results
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
continue-on-error: true
run: rm -r test-results
- name: check for changes
if: ${{ needs.conditions.outputs.should-run == 'true' && matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 && github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
if git status | grep -q "Changes to be committed"
then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit -m "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
git push
git push origin ${{ github.head_ref }}
# only upload artifacts if there's actually changes
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
with:
name: playwright-report-ubuntu-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
electron: electron:
needs: [conditions, prepare-wasm]
timeout-minutes: 60 timeout-minutes: 60
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }} name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
strategy: strategy:
@ -80,218 +293,94 @@ jobs:
shardTotal: [4] shardTotal: [4]
# TODO: add ref here for main and latest release tag # TODO: add ref here for main and latest release tag
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: conditions
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/checkout@v4
id: app-token if: needs.conditions.outputs.should-run == 'true'
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- id: filter
name: Check for Rust changes
uses: dorny/paths-filter@v3
with:
filters: |
rust:
- 'rust/**'
- uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
id: deps-install
if: needs.conditions.outputs.should-run == 'true'
shell: bash
run: yarn
- name: Cache Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
- name: Install Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
shell: bash
run: yarn playwright install --with-deps
- name: Download Wasm Cache
id: download-wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: rust/kcl-wasm-lib/pkg
- name: copy wasm blob
if: ${{ needs.conditions.outputs.should-run == 'true' && github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
shell: bash
run: cp rust/kcl-wasm-lib/pkg/kcl_wasm_lib_bg.wasm public
continue-on-error: true
- name: Build WASM condition
id: wasm
if: needs.conditions.outputs.should-run == 'true'
shell: bash
run: |
set -euox pipefail
# Build wasm if this is a scheduled run, there are Rust changes, or
# downloading from the wasm cache failed.
if [[ ${{github.event_name}} == 'schedule' || ${{steps.filter.outputs.rust}} == 'true' || ${{steps.download-wasm.outcome}} == 'failure' ]]; then
echo "should-build-wasm=true" >> $GITHUB_OUTPUT
else
echo "should-build-wasm=false" >> $GITHUB_OUTPUT
fi
- name: Use correct Rust toolchain
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
with:
tool: wasm-pack
- name: Rust Cache
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: install good sed
if: ${{ needs.conditions.outputs.should-run == 'true' && startsWith(matrix.os, 'macos') }}
shell: bash
run: |
brew install gnu-sed
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
- name: Install vector
shell: bash
# TODO: figure out what to do with this, it's failing
if: false
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
chmod +x /tmp/vector.sh
/tmp/vector.sh -y -no-modify-path
mkdir -p /tmp/vector
cp .github/workflows/vector.toml /tmp/vector.toml
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
cat /tmp/vector.toml
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
- name: Build Wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: yarn build:wasm
- name: build web
if: needs.conditions.outputs.should-run == 'true'
shell: bash
run: yarn tronb:vite:dev
- name: Run ubuntu/chrome snapshots
if: ${{ needs.conditions.outputs.should-run == 'true' && 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: |
yarn test:snapshots
env:
CI: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
with:
name: playwright-report-snapshots-${{ matrix.os }}-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true
- name: Clean up test-results
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
continue-on-error: true
run: rm -r test-results
- name: check for changes
if: ${{ needs.conditions.outputs.should-run == 'true' && matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 && github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
if git status | grep -q "Changes to be committed"
then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit -m "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
git push
git push origin ${{ github.head_ref }}
# only upload artifacts if there's actually changes
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
with:
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
- uses: actions/download-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
continue-on-error: true
with:
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
- name: Run playwright/electron flow (with retries)
id: retry
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && steps.deps-install.outcome == 'success' }}
uses: nick-fields/retry@v3.0.2
with:
shell: bash
command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
timeout_minutes: 30
max_attempts: 25
env:
CI: true
FAIL_ON_CONSOLE_ERRORS: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
with:
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
include-hidden-files: true
retention-days: 30
overwrite: true
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
with:
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true
- uses: actions/download-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
name: prepared-wasm
- name: Copy prepared wasm
if: needs.conditions.outputs.should-run == 'true'
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
id: deps-install
if: needs.conditions.outputs.should-run == 'true'
run: yarn
- name: Cache Playwright Browsers
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Build web
if: needs.conditions.outputs.should-run == 'true'
run: yarn tronb:vite:dev
- name: Install good sed
if: startsWith(matrix.os, 'macos')
shell: bash
run: |
brew install gnu-sed
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
# TODO: Add back axiom logs
- uses: actions/download-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
continue-on-error: true
with:
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
- name: Run playwright/electron flow (with retries)
id: retry
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && steps.deps-install.outcome == 'success' }}
uses: nick-fields/retry@v3.0.2
with:
shell: bash
command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
timeout_minutes: 30
max_attempts: 25
env:
CI: true
FAIL_ON_CONSOLE_ERRORS: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
with:
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
include-hidden-files: true
retention-days: 30
overwrite: true
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
with:
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true

View File

@ -40,9 +40,13 @@ jobs:
# cleanup old # cleanup old
rm -rf documentation/content/pages/docs/kcl/*.md rm -rf documentation/content/pages/docs/kcl/*.md
rm -rf documentation/content/pages/docs/kcl/types rm -rf documentation/content/pages/docs/kcl/types
rm -rf documentation/content/pages/docs/kcl/settings
rm -rf documentation/content/pages/docs/kcl/consts
# move new # move new
mv -f docs/kcl/*.md documentation/content/pages/docs/kcl/ mv -f docs/kcl/*.md documentation/content/pages/docs/kcl/
mv -f docs/kcl/types documentation/content/pages/docs/kcl/ mv -f docs/kcl/types documentation/content/pages/docs/kcl/
mv -f docs/kcl/settings documentation/content/pages/docs/kcl/
mv -f docs/kcl/consts documentation/content/pages/docs/kcl/
- name: move kcl-samples - name: move kcl-samples
shell: bash shell: bash
run: | run: |

File diff suppressed because one or more lines are too long

25
docs/kcl/consts.md Normal file
View File

@ -0,0 +1,25 @@
---
title: "KCL Constants"
excerpt: "Documentation for the KCL constants."
layout: manual
---
## Table of Contents
### `std`
- [`HALF_TURN`](/docs/kcl/consts/std-HALF_TURN)
- [`QUARTER_TURN`](/docs/kcl/consts/std-QUARTER_TURN)
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-THREE_QUARTER_TURN)
- [`XY`](/docs/kcl/consts/std-XY)
- [`XZ`](/docs/kcl/consts/std-XZ)
- [`YZ`](/docs/kcl/consts/std-YZ)
- [`ZERO`](/docs/kcl/consts/std-ZERO)
### `std::math`
- [`E`](/docs/kcl/consts/std-math-E)
- [`PI`](/docs/kcl/consts/std-math-PI)
- [`TAU`](/docs/kcl/consts/std-math-TAU)

View File

@ -6,7 +6,7 @@ layout: manual
Extend a 2-dimensional sketch through a third dimension in order to create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid. Extend a 2-dimensional sketch through a third dimension in order to create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid.
You can provide more than one sketch to extrude, and they will all be extruded in the same direction.
```js ```js
extrude( extrude(
@ -20,7 +20,7 @@ extrude(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketches should be extruded | Yes | | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch or set of sketches should be extruded | Yes |
| `length` | [`number`](/docs/kcl/types/number) | How far to extrude the given sketches | Yes | | `length` | [`number`](/docs/kcl/types/number) | How far to extrude the given sketches | Yes |
### Returns ### Returns

View File

@ -12,25 +12,26 @@ layout: manual
* [`Modules`](kcl/modules) * [`Modules`](kcl/modules)
* [`Settings`](kcl/settings) * [`Settings`](kcl/settings)
* [`Known Issues`](kcl/known-issues) * [`Known Issues`](kcl/known-issues)
* [`Constants`](kcl/consts)
### Standard library ### Standard library
* **Primitive types** * **Primitive types**
* [`bool`](kcl/bool) * [`bool`](kcl/types/bool)
* [`number`](kcl/number) * [`number`](kcl/types/number)
* [`string`](kcl/string) * [`string`](kcl/types/string)
* [`tag`](kcl/tag) * [`tag`](kcl/types/tag)
* **std** * **std**
* [`HALF_TURN`](kcl/const_std-HALF_TURN) * [`HALF_TURN`](kcl/consts/std-HALF_TURN)
* [`Plane`](kcl/Plane) * [`Plane`](kcl/types/Plane)
* [`QUARTER_TURN`](kcl/const_std-QUARTER_TURN) * [`QUARTER_TURN`](kcl/consts/std-QUARTER_TURN)
* [`Sketch`](kcl/Sketch) * [`Sketch`](kcl/types/Sketch)
* [`Solid`](kcl/Solid) * [`Solid`](kcl/types/Solid)
* [`THREE_QUARTER_TURN`](kcl/const_std-THREE_QUARTER_TURN) * [`THREE_QUARTER_TURN`](kcl/consts/std-THREE_QUARTER_TURN)
* [`XY`](kcl/const_std-XY) * [`XY`](kcl/consts/std-XY)
* [`XZ`](kcl/const_std-XZ) * [`XZ`](kcl/consts/std-XZ)
* [`YZ`](kcl/const_std-YZ) * [`YZ`](kcl/consts/std-YZ)
* [`ZERO`](kcl/const_std-ZERO) * [`ZERO`](kcl/consts/std-ZERO)
* [`abs`](kcl/abs) * [`abs`](kcl/abs)
* [`acos`](kcl/acos) * [`acos`](kcl/acos)
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX) * [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
@ -134,9 +135,9 @@ layout: manual
* [`yLine`](kcl/yLine) * [`yLine`](kcl/yLine)
* [`yd`](kcl/yd) * [`yd`](kcl/yd)
* **std::math** * **std::math**
* [`E`](kcl/const_std-math-E) * [`E`](kcl/consts/std-math-E)
* [`PI`](kcl/const_std-math-PI) * [`PI`](kcl/consts/std-math-PI)
* [`TAU`](kcl/const_std-math-TAU) * [`TAU`](kcl/consts/std-math-TAU)
* [`cos`](kcl/std-math-cos) * [`cos`](kcl/std-math-cos)
* [`sin`](kcl/std-math-sin) * [`sin`](kcl/std-math-sin)
* [`tan`](kcl/std-math-tan) * [`tan`](kcl/std-math-tan)

View File

@ -13,9 +13,7 @@ once fixed in engine will just start working here with no language changes.
If you see a red line around your model, it means this is happening. If you see a red line around your model, it means this is happening.
- **Import**: Right now you can import a file, even if that file has brep data - **Import**: Right now you can import a file, even if that file has brep data
you cannot edit it, after v1, the engine will account for this. You also cannot you cannot edit it, after v1, the engine will account for this.
currently move or transform the imported objects at all, once we have assemblies
this will work.
- **Fillets**: Fillets cannot intersect, you will get an error. Only simple fillet - **Fillets**: Fillets cannot intersect, you will get an error. Only simple fillet
cases work currently. cases work currently.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -24,6 +24,5 @@ A face.
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | 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 | | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -22,6 +22,5 @@ A helix.
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No | | `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -22,6 +22,5 @@ A helix.
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No | | `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -18,6 +18,5 @@ Data for an imported geometry.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No | | `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No | | `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -25,7 +25,6 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Uuid`| | No | | `type` |enum: `Uuid`| | No |
| `value` |[`string`](/docs/kcl/types/string)| | No | | `value` |[`string`](/docs/kcl/types/string)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -42,7 +41,6 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Bool`| | No | | `type` |enum: `Bool`| | No |
| `value` |`boolean`| | No | | `value` |`boolean`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -60,7 +58,6 @@ Any KCL value.
| `type` |enum: `Number`| | No | | `type` |enum: `Number`| | No |
| `value` |[`number`](/docs/kcl/types/number)| | No | | `value` |[`number`](/docs/kcl/types/number)| | No |
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No | | `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -77,7 +74,6 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `String`| | No | | `type` |enum: `String`| | No |
| `value` |[`string`](/docs/kcl/types/string)| | No | | `value` |[`string`](/docs/kcl/types/string)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -94,7 +90,6 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `MixedArray`| | No | | `type` |enum: `MixedArray`| | No |
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No | | `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -111,7 +106,6 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Object`| | No | | `type` |enum: `Object`| | No |
| `value` |`object`| | No | | `value` |`object`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -129,7 +123,6 @@ Any KCL value.
| `type` |enum: [`TagIdentifier`](/docs/kcl/types#tag-identifier)| | No | | `type` |enum: [`TagIdentifier`](/docs/kcl/types#tag-identifier)| | No |
| `value` |[`string`](/docs/kcl/types/string)| | No | | `value` |[`string`](/docs/kcl/types/string)| | No |
| `info` |[`TagEngineInfo`](/docs/kcl/types/TagEngineInfo)| | No | | `info` |[`TagEngineInfo`](/docs/kcl/types/TagEngineInfo)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -279,7 +272,6 @@ Data for an imported geometry.
| `type` |enum: [`ImportedGeometry`](/docs/kcl/types/ImportedGeometry)| | No | | `type` |enum: [`ImportedGeometry`](/docs/kcl/types/ImportedGeometry)| | No |
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No | | `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No | | `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -295,7 +287,6 @@ Data for an imported geometry.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Function`| | No | | `type` |enum: `Function`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -312,7 +303,6 @@ Data for an imported geometry.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Module`| | No | | `type` |enum: `Module`| | No |
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No | | `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -328,7 +318,6 @@ Data for an imported geometry.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Type`| | No | | `type` |enum: `Type`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -345,7 +334,6 @@ Data for an imported geometry.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No | | `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No |
| `value` |[`KclNone`](/docs/kcl/types/KclNone)| KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application). | No | | `value` |[`KclNone`](/docs/kcl/types/KclNone)| KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application). | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -362,7 +350,6 @@ Data for an imported geometry.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Tombstone`| | No | | `type` |enum: `Tombstone`| | No |
| `value` |`null`| | No | | `value` |`null`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----

View File

@ -32,7 +32,6 @@ A sketch or a group of sketches.
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No | | `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
| `originalId` |[`string`](/docs/kcl/types/string)| | No | | `originalId` |[`string`](/docs/kcl/types/string)| | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
---- ----

View File

@ -32,7 +32,6 @@ A sketch type.
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y 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 | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -57,7 +56,6 @@ A face.
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | 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 | | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----

View File

@ -0,0 +1,60 @@
---
title: "SolidOrImportedGeometry"
excerpt: "Data for a solid or an imported geometry."
layout: manual
---
Data for a solid or an imported geometry.
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `solid`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the solid. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID of the solid. Unlike `id`, this doesn't change. | No |
| `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No |
| `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No |
| `height` |[`number`](/docs/kcl/types/number)| The height of the solid. | No |
| `startCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion start cap | No |
| `endCapId` |[`string`](/docs/kcl/types/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 unit of length. | No |
----
Data for an imported geometry.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `importedGeometry`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
----

View File

@ -33,7 +33,6 @@ A solid or a group of solids.
| `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No | | `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
---- ----

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test' import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { import {
getUtils, getUtils,
TEST_COLORS, TEST_COLORS,

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test' import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { HomePageFixture } from './fixtures/homePageFixture' import { HomePageFixture } from './fixtures/homePageFixture'
import { getUtils } from './test-utils' import { getUtils } from './test-utils'
import { EngineCommand } from 'lang/std/artifactGraph' import { EngineCommand } from 'lang/std/artifactGraph'

View File

@ -10,7 +10,11 @@ import fsp from 'fs/promises'
test( test(
'export works on the first try', 'export works on the first try',
{ tag: ['@electron', '@skipLocalEngine'] }, { tag: ['@electron', '@skipLocalEngine'] },
async ({ page, context, scene }, testInfo) => { async ({ page, context, scene, tronApp }, testInfo) => {
if (!tronApp) {
fail()
}
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })]) await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
@ -86,7 +90,7 @@ test(
await expect(exportingToastMessage).not.toBeVisible() await expect(exportingToastMessage).not.toBeVisible()
const firstFileFullPath = path.resolve( const firstFileFullPath = path.resolve(
getPlaywrightDownloadDir(page), getPlaywrightDownloadDir(tronApp.projectDirName),
exportFileName exportFileName
) )
await test.step('Check the export size', async () => { await test.step('Check the export size', async () => {
@ -165,7 +169,7 @@ test(
])) ]))
const secondFileFullPath = path.resolve( const secondFileFullPath = path.resolve(
getPlaywrightDownloadDir(page), getPlaywrightDownloadDir(tronApp.projectDirName),
exportFileName exportFileName
) )
await test.step('Check the export size', async () => { await test.step('Check the export size', async () => {

View File

@ -274,7 +274,6 @@ test.describe('Feature Tree pane', () => {
currentArgKey: 'distance', currentArgKey: 'distance',
currentArgValue: initialInput, currentArgValue: initialInput,
headerArguments: { headerArguments: {
Selection: '1 face',
Distance: initialInput, Distance: initialInput,
}, },
highlightedHeaderArg: 'distance', highlightedHeaderArg: 'distance',
@ -291,7 +290,6 @@ test.describe('Feature Tree pane', () => {
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
Selection: '1 face',
// The calculated value is shown in the argument summary // The calculated value is shown in the argument summary
Distance: initialInput, Distance: initialInput,
}, },

View File

@ -158,11 +158,14 @@ test.describe('when using the file tree to', () => {
await createNewFile('lee') await createNewFile('lee')
await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => { await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => {
await expect( await expect
page .poll(() =>
.locator('[data-testid="file-pane-scroll-container"] button') page
.filter({ hasText: /lee[-]?[0-5]?/ }) .locator('[data-testid="file-pane-scroll-container"] button')
).toHaveCount(5) .filter({ hasText: /lee[-]?[0-5]?/ })
.count()
)
.toEqual(5)
}) })
} }
) )

View File

@ -27,28 +27,19 @@ type CmdBarSerialised =
export class CmdBarFixture { export class CmdBarFixture {
public page: Page public page: Page
public cmdBarOpenBtn!: Locator
get cmdBarOpenBtn() { public cmdBarElement!: Locator
return this.page.getByTestId('command-bar-open-button')
}
get cmdBarElement() {
return this.page.getByTestId('command-bar')
}
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.cmdBarOpenBtn = this.page.getByTestId('command-bar-open-button')
this.cmdBarElement = this.page.getByTestId('command-bar')
} }
get currentArgumentInput() { get currentArgumentInput() {
return this.page.getByTestId('cmd-bar-arg-value') return this.page.getByTestId('cmd-bar-arg-value')
} }
// Put all selectors here because this method is re-run on fixture creation.
reConstruct = (page: Page) => {
this.page = page
}
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => { private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) { if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
return { stage: 'commandBarClosed' } return { stage: 'commandBarClosed' }

View File

@ -24,11 +24,6 @@ export class EditorFixture {
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.reConstruct(page)
}
reConstruct = (page: Page) => {
this.page = page
this.codeContent = page.locator('.cm-content[data-language="kcl"]') this.codeContent = page.locator('.cm-content[data-language="kcl"]')
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint') this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error') this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
@ -87,6 +82,30 @@ export class EditorFixture {
toContain: this._expectEditorToContain(), toContain: this._expectEditorToContain(),
not: { toContain: this._expectEditorToContain(true) }, not: { toContain: this._expectEditorToContain(true) },
} }
snapshot = async (options?: { timeout?: number; name?: string }) => {
const wasPaneOpen = await this.checkIfPaneIsOpen()
if (!wasPaneOpen) {
await this.openPane()
}
try {
// Use expect.poll to implement retry logic
await expect
.poll(
async () => {
const code = await this.codeContent.textContent()
return code || ''
},
{ timeout: options?.timeout || 5000 }
)
.toMatchSnapshot(options?.name || 'editor-content')
} finally {
// Reset pane state if needed
if (!wasPaneOpen) {
await this.closePane()
}
}
}
private _serialiseDiagnostics = async (): Promise<Array<string>> => { private _serialiseDiagnostics = async (): Promise<Array<string>> => {
const diagnostics = await this.diagnosticsGutterIcon.all() const diagnostics = await this.diagnosticsGutterIcon.all()
const diagnosticsContent: string[] = [] const diagnosticsContent: string[] = []

View File

@ -1,13 +1,31 @@
/* eslint-disable react-hooks/rules-of-hooks */
import type { import type {
BrowserContext, BrowserContext,
ElectronApplication, ElectronApplication,
Fixtures as PlaywrightFixtures,
TestInfo, TestInfo,
Page, Page,
} from '@playwright/test' } from '@playwright/test'
import { getUtils, setup, setupElectron } from '../test-utils' import {
_electron as electron,
PlaywrightTestArgs,
PlaywrightWorkerArgs,
} from '@playwright/test'
import * as TOML from '@iarna/toml'
import {
TEST_SETTINGS_KEY,
TEST_SETTINGS_CORRUPTED,
TEST_SETTINGS,
TEST_SETTINGS_DEFAULT_THEME,
} from '../storageStates'
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
import { getUtils, setup } from '../test-utils'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import { join } from 'path' import fs from 'node:fs'
import path from 'path'
import { CmdBarFixture } from './cmdBarFixture' import { CmdBarFixture } from './cmdBarFixture'
import { EditorFixture } from './editorFixture' import { EditorFixture } from './editorFixture'
import { ToolbarFixture } from './toolbarFixture' import { ToolbarFixture } from './toolbarFixture'
@ -23,7 +41,7 @@ export class AuthenticatedApp {
public readonly testInfo: TestInfo public readonly testInfo: TestInfo
public readonly viewPortSize = { width: 1200, height: 500 } public readonly viewPortSize = { width: 1200, height: 500 }
public electronApp: undefined | ElectronApplication public electronApp: undefined | ElectronApplication
public dir: string = '' public projectDirName: string = ''
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
this.context = context this.context = context
@ -46,7 +64,7 @@ export class AuthenticatedApp {
} }
getInputFile = (fileName: string) => { getInputFile = (fileName: string) => {
return fsp.readFile( return fsp.readFile(
join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName), path.join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName),
'utf-8' 'utf-8'
) )
} }
@ -59,101 +77,300 @@ export interface Fixtures {
scene: SceneFixture scene: SceneFixture
homePage: HomePageFixture homePage: HomePageFixture
} }
export class AuthenticatedTronApp {
public originalPage: Page
public page: Page
public browserContext: BrowserContext
public context: BrowserContext
public readonly testInfo: TestInfo
public electronApp: ElectronApplication | undefined
public readonly viewPortSize = { width: 1200, height: 500 }
public dir: string = ''
constructor( export class ElectronZoo {
browserContext: BrowserContext, public available: boolean = true
originalPage: Page, public electron!: ElectronApplication
testInfo: TestInfo public firstUrl = ''
) { public viewPortSize = { width: 1200, height: 500 }
this.page = originalPage public projectDirName = ''
this.originalPage = originalPage
this.browserContext = browserContext public page!: Page
// Will be overwritten in the initializer public context!: BrowserContext
this.context = browserContext
this.testInfo = testInfo constructor() {}
}
async initialise( async makeAvailableAgain() {
arg: { // Help remote end by signaling we're done with the connection.
fixtures: Partial<Fixtures> await this.page.evaluate(async () => {
folderSetupFn?: (projectDirName: string) => Promise<void> return new Promise((resolve) => {
cleanProjectDir?: boolean if (!window.engineCommandManager.engineConnection?.state?.type) {
appSettings?: DeepPartial<Settings> return resolve(undefined)
} = { fixtures: {} } }
) {
const { electronApp, page, context, dir } = await setupElectron({ window.engineCommandManager.tearDown()
testInfo: this.testInfo, // Keep polling (per js event tick) until state is Disconnected.
folderSetupFn: arg.folderSetupFn, const checkDisconnected = () => {
cleanProjectDir: arg.cleanProjectDir, // It's possible we never even created an engineConnection
appSettings: arg.appSettings, // e.g. never left Projects view.
viewport: this.viewPortSize, if (
window.engineCommandManager?.engineConnection?.state.type ===
'disconnected'
) {
return resolve(undefined)
}
setTimeout(checkDisconnected, 0)
}
checkDisconnected()
})
}) })
this.page = page
// These assignments "fix" some brokenness in the Playwright Workbench when await this.context.tracing.stopChunk({ path: 'trace.zip' })
// running against electron applications.
// The timeline is still broken but failure screenshots work again.
this.context = context
// TODO: try to get this to work again for screenshots, but it messed with test ends when enabled
// Object.assign(this.browserContext, this.context)
this.electronApp = electronApp // Only after cleanup we're ready.
this.dir = dir this.available = true
}
// Easier to access throughout utils async createInstanceIfMissing(testInfo: TestInfo) {
this.page.dir = dir // Create or otherwise clear the folder.
this.projectDirName = testInfo.outputPath('electron-test-projects-dir')
// Setup localStorage, addCookies, reload // We need to expose this in order for some tests that require folder
await setup(this.context, this.page, this.testInfo) // creation and some code below.
const that = this
for (const key of unsafeTypedKeys(arg.fixtures)) { const options = {
const fixture = arg.fixtures[key] args: ['.', '--no-sandbox'],
if ( env: {
!fixture || ...process.env,
fixture instanceof AuthenticatedApp || TEST_SETTINGS_FILE_KEY: this.projectDirName,
fixture instanceof AuthenticatedTronApp IS_PLAYWRIGHT: 'true',
) },
continue ...(process.env.ELECTRON_OVERRIDE_DIST_PATH
fixture.reConstruct(page) ? {
executablePath:
process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron',
}
: {}),
...(process.env.PLAYWRIGHT_RECORD_VIDEO
? {
recordVideo: {
dir: testInfo.snapshotPath(),
size: this.viewPortSize,
},
}
: {}),
} }
// Do this once and then reuse window on subsequent calls.
if (!this.electron) {
this.electron = await electron.launch(options)
this.context = this.electron.context()
this.page = await this.electron.firstWindow()
await this.context.tracing.start({ screenshots: true, snapshots: true })
}
await this.context.tracing.startChunk()
await setup(this.context, this.page, testInfo)
await this.cleanProjectDir()
// Create a consistent way to resize the page across electron and web.
// (lee) I had to do everything in the book to make electron change its
// damn window size. I succeeded in making it consistently and reliably
// do it after a whole afternoon.
this.page.setBodyDimensions = async function (dims: {
width: number
height: number
}) {
await this.setViewportSize(dims)
await that.electron?.evaluateHandle(async ({ app }, dims) => {
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
await app.resizeWindow(dims.width, dims.height)
}, dims)
return this.evaluate(async (dims: { width: number; height: number }) => {
await window.electron.resizeWindow(dims.width, dims.height)
window.document.body.style.width = dims.width + 'px'
window.document.body.style.height = dims.height + 'px'
window.document.documentElement.style.width = dims.width + 'px'
window.document.documentElement.style.height = dims.height + 'px'
}, dims)
}
await this.page.setBodyDimensions(this.viewPortSize)
this.context.folderSetupFn = async function (fn) {
return fn(that.projectDirName)
.then(() => that.page.reload())
.then(() => ({
dir: that.projectDirName,
}))
}
// We need to patch this because addInitScript will bind too late in our
// electron tests, never running. We need to call reload() after each call
// to guarantee it runs.
const oldContextAddInitScript = this.context.addInitScript
this.context.addInitScript = async function (a, b) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldContextAddInitScript.apply(this, [a, b])
await that.page.reload()
}
// No idea why we mix and match page and context's addInitScript but we do
const oldPageAddInitScript = this.page.addInitScript
this.page.addInitScript = async function (a: any, b: any) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldPageAddInitScript.apply(this, [a, b])
await that.page.reload()
}
if (!this.firstUrl) {
await this.page.getByText('Your Projects').count()
this.firstUrl = this.page.url()
}
// Due to the app controlling its own window context we need to inject new
// options and context here.
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
// await tronApp.electronApp.evaluate(({ app }) => {
// return app.reuseWindowForTest();
// });
await this.electron?.evaluate(({ app }, projectDirName) => {
// @ts-ignore can't declaration merge see main.ts
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
}, this.projectDirName)
// Always start at the root view
await this.page.goto(this.firstUrl)
// Force a hard reload, destroying the stream and other state
await this.page.reload()
} }
close = async () => { async cleanProjectDir(appSettings?: DeepPartial<Settings>) {
await this.electronApp?.close?.() try {
if (fs.existsSync(this.projectDirName)) {
await fsp.rm(this.projectDirName, { recursive: true })
}
} catch (e) {
console.error(e)
}
try {
await fsp.mkdir(this.projectDirName)
} catch (e) {
// Not a problem if it already exists.
}
const tempSettingsFilePath = path.join(
this.projectDirName,
SETTINGS_FILE_NAME
)
let settingsOverridesToml = ''
if (appSettings) {
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
project_directory: this.projectDirName,
...appSettings.app,
},
},
})
} else {
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
app: {
...TEST_SETTINGS.app,
project_directory: this.projectDirName,
},
},
})
}
await fsp.writeFile(tempSettingsFilePath, settingsOverridesToml)
} }
debugPause = () =>
new Promise(() => {
console.log('UN-RESOLVING PROMISE')
})
} }
export const fixtures = { // If yee encounter this, please try to type it.
cmdBar: async ({ page }: { page: Page }, use: any) => { type FnUse = any
// eslint-disable-next-line react-hooks/rules-of-hooks
const fixturesForElectron = {
page: async (
{ tronApp }: { tronApp: ElectronZoo },
use: FnUse,
testInfo: TestInfo
) => {
await tronApp.createInstanceIfMissing(testInfo)
await use(tronApp.page)
await tronApp?.makeAvailableAgain()
},
context: async (
{ tronApp }: { tronApp: ElectronZoo },
use: FnUse,
testInfo: TestInfo
) => {
await tronApp.createInstanceIfMissing(testInfo)
await use(tronApp.context)
},
}
const fixturesForWeb = {
page: async (
{ page, context }: { page: Page; context: BrowserContext },
use: FnUse,
testInfo: TestInfo
) => {
page.setBodyDimensions = page.setViewportSize
// We do the same thing in ElectronZoo. addInitScript simply doesn't fire
// at the correct time, so we reload the page and it fires appropriately.
const oldPageAddInitScript = page.addInitScript
page.addInitScript = async function (...args) {
// @ts-expect-error
await oldPageAddInitScript.apply(this, args)
await page.reload()
}
const oldContextAddInitScript = context.addInitScript
context.addInitScript = async function (...args) {
// @ts-expect-error
await oldContextAddInitScript.apply(this, args)
await page.reload()
}
const webApp = new AuthenticatedApp(context, page, testInfo)
await webApp.initialise()
await use(page)
},
}
const fixturesBasedOnProcessEnvPlatform = {
cmdBar: async ({ page }: { page: Page }, use: FnUse) => {
await use(new CmdBarFixture(page)) await use(new CmdBarFixture(page))
}, },
editor: async ({ page }: { page: Page }, use: any) => { editor: async ({ page }: { page: Page }, use: FnUse) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new EditorFixture(page)) await use(new EditorFixture(page))
}, },
toolbar: async ({ page }: { page: Page }, use: any) => { toolbar: async ({ page }: { page: Page }, use: FnUse) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new ToolbarFixture(page)) await use(new ToolbarFixture(page))
}, },
scene: async ({ page }: { page: Page }, use: any) => { scene: async ({ page }: { page: Page }, use: FnUse) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new SceneFixture(page)) await use(new SceneFixture(page))
}, },
homePage: async ({ page }: { page: Page }, use: any) => { homePage: async ({ page }: { page: Page }, use: FnUse) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new HomePageFixture(page)) await use(new HomePageFixture(page))
}, },
} }
if (process.env.PLATFORM === 'web') {
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForWeb)
} else {
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForElectron)
}
export { fixturesBasedOnProcessEnvPlatform }

View File

@ -27,10 +27,6 @@ export class HomePageFixture {
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.reConstruct(page)
}
reConstruct = (page: Page) => {
this.page = page
this.projectSection = this.page.getByTestId('home-section') this.projectSection = this.page.getByTestId('home-section')
@ -96,8 +92,12 @@ export class HomePageFixture {
} }
} }
createAndGoToProject = async (projectTitle = 'project-$nnn') => { projectsLoaded = async () => {
await expect(this.projectSection).not.toHaveText('Loading your Projects...') await expect(this.projectSection).not.toHaveText('Loading your Projects...')
}
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
await this.projectsLoaded()
await this.projectButtonNew.click() await this.projectButtonNew.click()
await this.projectTextName.click() await this.projectTextName.click()
await this.projectTextName.fill(projectTitle) await this.projectTextName.fill(projectTitle)

View File

@ -53,7 +53,12 @@ export class SceneFixture {
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.reConstruct(page) this.streamWrapper = page.getByTestId('stream')
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
} }
private _serialiseScene = async (): Promise<SceneSerialised> => { private _serialiseScene = async (): Promise<SceneSerialised> => {
const camera = await this.getCameraInfo() const camera = await this.getCameraInfo()
@ -72,17 +77,6 @@ export class SceneFixture {
.toEqual(expected) .toEqual(expected)
} }
reConstruct = (page: Page) => {
this.page = page
this.streamWrapper = page.getByTestId('stream')
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
}
makeMouseHelpers = ( makeMouseHelpers = (
x: number, x: number,
y: number, y: number,
@ -253,7 +247,7 @@ export class SceneFixture {
await u.openDebugPanel() await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel() await u.closeDebugPanel()
await this.waitForExecutionDone() await this.waitForExecutionDone()
await expect(this.startEditSketchBtn).not.toBeDisabled() await expect(this.startEditSketchBtn).not.toBeDisabled()

View File

@ -37,13 +37,12 @@ export class ToolbarFixture {
featureTreeId = 'feature-tree' as const featureTreeId = 'feature-tree' as const
/** The pane element for the Feature Tree */ /** The pane element for the Feature Tree */
featureTreePane!: Locator featureTreePane!: Locator
gizmo!: Locator
gizmoDisabled!: Locator
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.reConstruct(page)
}
reConstruct = (page: Page) => {
this.page = page
this.extrudeButton = page.getByTestId('extrude') this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft') this.loftButton = page.getByTestId('loft')
this.sweepButton = page.getByTestId('sweep') this.sweepButton = page.getByTestId('sweep')
@ -67,6 +66,13 @@ export class ToolbarFixture {
this.filePane = page.locator('#files-pane') this.filePane = page.locator('#files-pane')
this.featureTreePane = page.locator('#feature-tree-pane') this.featureTreePane = page.locator('#feature-tree-pane')
this.fileCreateToast = page.getByText('Successfully created') this.fileCreateToast = page.getByText('Successfully created')
// Note to test writers: having two locators like this is preferable to one
// which changes another el property because it means our test "signal" is
// completely decoupled from the elements themselves. It means the same
// element or two different elements can represent these states.
this.gizmo = page.getByTestId('gizmo')
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
} }
get editSketchBtn() { get editSketchBtn() {
@ -86,6 +92,18 @@ export class ToolbarFixture {
startSketchPlaneSelection = async () => startSketchPlaneSelection = async () =>
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500) doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
waitUntilSketchingReady = async () => {
await expect(this.gizmoDisabled).toBeVisible()
}
startSketchThenCallbackThenWaitUntilReady = async (
cb: () => Promise<void>
) => {
await this.startSketchBtn.click()
await cb()
await this.waitUntilSketchingReady()
}
exitSketch = async () => { exitSketch = async () => {
await this.exitSketchBtn.click() await this.exitSketchBtn.click()
await expect( await expect(

View File

@ -21,58 +21,54 @@ import { expectPixelColor } from './fixtures/sceneFixture'
// we must set it to empty for the tests where we want to see the onboarding immediately. // we must set it to empty for the tests where we want to see the onboarding immediately.
test.describe('Onboarding tests', () => { test.describe('Onboarding tests', () => {
test( test('Onboarding code is shown in the editor', async ({
'Onboarding code is shown in the editor', page,
{ homePage,
appSettings: { tronApp,
app: { }) => {
onboarding_status: '', if (!tronApp) {
}, fail()
},
cleanProjectDir: true,
},
async ({ page, homePage }) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
// *and* that the code is shown in the editor
await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
// Make sure the model loaded
const XYPlanePoint = { x: 774, y: 116 } as const
const modelColor: [number, number, number] = [45, 45, 45]
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(
8
)
} }
) await tronApp.cleanProjectDir({
app: {
onboarding_status: '',
},
})
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
// Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
// Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
// *and* that the code is shown in the editor
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
// Make sure the model loaded
const XYPlanePoint = { x: 774, y: 116 } as const
const modelColor: [number, number, number] = [45, 45, 45]
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(8)
})
test( test(
'Desktop: fresh onboarding executes and loads', 'Desktop: fresh onboarding executes and loads',
{ {
tag: '@electron', tag: '@electron',
appSettings: { },
async ({ page, tronApp }) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
onboarding_status: '', onboarding_status: '',
}, },
}, })
cleanProjectDir: true,
},
async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 } const viewportSize = { width: 1200, height: 500 }
@ -107,223 +103,235 @@ test.describe('Onboarding tests', () => {
} }
) )
test( test('Code resets after confirmation', async ({
'Code resets after confirmation', context,
{ page,
cleanProjectDir: true, homePage,
}, tronApp,
async ({ context, page, homePage }) => { scene,
const initialCode = `sketch001 = startSketchOn('XZ')` cmdBar,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir()
// Load the page up with some code so we see the confirmation warning const initialCode = `sketch001 = startSketchOn('XZ')`
// when we go to replay onboarding
await context.addInitScript((code) => {
localStorage.setItem('persistCode', code)
}, initialCode)
await page.setBodyDimensions({ width: 1200, height: 500 }) // Load the page up with some code so we see the confirmation warning
await homePage.goToModelingScene() // when we go to replay onboarding
await page.addInitScript((code) => {
localStorage.setItem('persistCode', code)
}, initialCode)
// Replay the onboarding await page.setBodyDimensions({ width: 1200, height: 500 })
await page.getByRole('link', { name: 'Settings' }).last().click() await homePage.goToModelingScene()
const replayButton = page.getByRole('button', { await scene.connectionEstablished()
name: 'Replay onboarding',
})
await expect(replayButton).toBeVisible()
await replayButton.click()
// Ensure we see the warning, and that the code has not yet updated // Replay the onboarding
await expect(page.getByText('Would you like to create')).toBeVisible() await page.getByRole('link', { name: 'Settings' }).last().click()
await expect(page.locator('.cm-content')).toHaveText(initialCode) const replayButton = page.getByRole('button', {
name: 'Replay onboarding',
})
await expect(replayButton).toBeVisible()
await replayButton.click()
const nextButton = page.getByTestId('onboarding-next') // Ensure we see the warning, and that the code has not yet updated
await expect(page.getByText('Would you like to create')).toBeVisible()
await expect(page.locator('.cm-content')).toHaveText(initialCode)
const nextButton = page.getByTestId('onboarding-next')
await nextButton.hover()
await nextButton.click()
// Ensure we see the introduction and that the code has been reset
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
// There used to be old code here that checked if we stored the reset
// code into localStorage but that isn't the case on desktop. It gets
// saved to the file system, which we have other tests for.
})
test('Click through each onboarding step and back', async ({
context,
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '',
},
})
// Override beforeEach test setup
await context.addInitScript(
async ({ settingsKey, settings }) => {
// Give no initial code, so that the onboarding start is shown immediately
localStorage.setItem('persistCode', '')
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_START,
}),
}
)
await page.setBodyDimensions({ width: 1200, height: 1080 })
await homePage.goToModelingScene()
// Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
const nextButton = page.getByTestId('onboarding-next')
const prevButton = page.getByTestId('onboarding-prev')
while ((await nextButton.innerText()) !== 'Finish') {
await nextButton.hover() await nextButton.hover()
await nextButton.click() await nextButton.click()
// Ensure we see the introduction and that the code has been reset
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
// There used to be old code here that checked if we stored the reset
// code into localStorage but that isn't the case on desktop. It gets
// saved to the file system, which we have other tests for.
} }
)
test( while ((await prevButton.innerText()) !== 'Dismiss') {
'Click through each onboarding step and back',
{
appSettings: {
app: {
onboarding_status: '',
},
},
},
async ({ context, page, homePage }) => {
// Override beforeEach test setup
await context.addInitScript(
async ({ settingsKey, settings }) => {
// Give no initial code, so that the onboarding start is shown immediately
localStorage.setItem('persistCode', '')
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_START,
}),
}
)
await page.setBodyDimensions({ width: 1200, height: 1080 })
await homePage.goToModelingScene()
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
const nextButton = page.getByTestId('onboarding-next')
const prevButton = page.getByTestId('onboarding-prev')
while ((await nextButton.innerText()) !== 'Finish') {
await nextButton.hover()
await nextButton.click()
}
while ((await prevButton.innerText()) !== 'Dismiss') {
await prevButton.hover()
await prevButton.click()
}
// Dismiss the onboarding
await prevButton.hover() await prevButton.hover()
await prevButton.click() await prevButton.click()
// Test that the onboarding pane is gone
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect.poll(() => page.url()).not.toContain('/onboarding')
} }
)
test( // Dismiss the onboarding
'Onboarding redirects and code updating', await prevButton.hover()
{ await prevButton.click()
appSettings: {
app: { // Test that the onboarding pane is gone
onboarding_status: '/export', await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
}, await expect.poll(() => page.url()).not.toContain('/onboarding')
})
test('Onboarding redirects and code updating', async ({
context,
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '/export',
}, },
cleanProjectDir: true, })
},
async ({ context, page, homePage }) => {
const originalCode = 'sigmaAllow = 15000'
// Override beforeEach test setup const originalCode = 'sigmaAllow = 15000'
await context.addInitScript(
async ({ settingsKey, settings }) => {
// Give some initial code, so we can test that it's cleared
localStorage.setItem('persistCode', originalCode)
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
}),
}
)
await page.setBodyDimensions({ width: 1200, height: 500 }) // Override beforeEach test setup
await homePage.goToModelingScene() await context.addInitScript(
async ({ settingsKey, settings }) => {
// Test that the redirect happened // Give some initial code, so we can test that it's cleared
await expect.poll(() => page.url()).toContain('/onboarding/export') localStorage.setItem('persistCode', originalCode)
localStorage.setItem(settingsKey, settings)
// Test that you come back to this page when you refresh
await page.reload()
await expect.poll(() => page.url()).toContain('/onboarding/export')
// Test that the code changes when you advance to the next step
await page.getByTestId('onboarding-next').hover()
await page.getByTestId('onboarding-next').click()
// Test that the onboarding pane loaded
const title = page.locator('[data-testid="onboarding-content"]')
await expect(title).toBeAttached()
await expect(page.locator('.cm-content')).not.toHaveText(originalCode)
// Test that the code is not empty when you click on the next step
await page.locator('[data-testid="onboarding-next"]').hover()
await page.locator('[data-testid="onboarding-next"]').click()
await expect(page.locator('.cm-content')).toHaveText(/.+/)
}
)
test(
'Onboarding code gets reset to demo on Interactive Numbers step',
{
appSettings: {
app: {
onboarding_status: '/parametric-modeling',
},
}, },
cleanProjectDir: true, {
}, settingsKey: TEST_SETTINGS_KEY,
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
}),
}
)
async ({ page, homePage }) => { await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page) await homePage.goToModelingScene()
const badCode = `// This is bad code we shouldn't see`
await page.setBodyDimensions({ width: 1200, height: 1080 }) // Test that the redirect happened
await homePage.goToModelingScene() await expect.poll(() => page.url()).toContain('/onboarding/export')
await expect // Test that you come back to this page when you refresh
.poll(() => page.url()) await page.reload()
.toContain(onboardingPaths.PARAMETRIC_MODELING) await expect.poll(() => page.url()).toContain('/onboarding/export')
const bracketNoNewLines = bracket.replace(/\n/g, '') // Test that the code changes when you advance to the next step
await page.getByTestId('onboarding-next').hover()
await page.getByTestId('onboarding-next').click()
// Check the code got reset on load // Test that the onboarding pane loaded
await expect(page.locator('#code-pane')).toBeVisible() const title = page.locator('[data-testid="onboarding-content"]')
await expect(u.codeLocator).toHaveText(bracketNoNewLines, { await expect(title).toBeAttached()
timeout: 10_000,
})
// Mess with the code again await expect(page.locator('.cm-content')).not.toHaveText(originalCode)
await u.codeLocator.selectText()
await u.codeLocator.fill(badCode)
await expect(u.codeLocator).toHaveText(badCode)
// Click to the next step // Test that the code is not empty when you click on the next step
await page.locator('[data-testid="onboarding-next"]').hover() await page.locator('[data-testid="onboarding-next"]').hover()
await page.locator('[data-testid="onboarding-next"]').click() await page.locator('[data-testid="onboarding-next"]').click()
await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, { await expect(page.locator('.cm-content')).toHaveText(/.+/)
waitUntil: 'domcontentloaded', })
})
// Check that the code has been reset test('Onboarding code gets reset to demo on Interactive Numbers step', async ({
await expect(u.codeLocator).toHaveText(bracketNoNewLines) page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
} }
) await tronApp.cleanProjectDir({
app: {
onboarding_status: '/parametric-modeling',
},
})
const u = await getUtils(page)
const badCode = `// This is bad code we shouldn't see`
await page.setBodyDimensions({ width: 1200, height: 1080 })
await homePage.goToModelingScene()
await expect
.poll(() => page.url())
.toContain(onboardingPaths.PARAMETRIC_MODELING)
const bracketNoNewLines = bracket.replace(/\n/g, '')
// Check the code got reset on load
await expect(page.locator('#code-pane')).toBeVisible()
await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
timeout: 10_000,
})
// Mess with the code again
await u.codeLocator.selectText()
await u.codeLocator.fill(badCode)
await expect(u.codeLocator).toHaveText(badCode)
// Click to the next step
await page.locator('[data-testid="onboarding-next"]').hover()
await page.locator('[data-testid="onboarding-next"]').click()
await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, {
waitUntil: 'domcontentloaded',
})
// Check that the code has been reset
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
})
// (lee) The two avatar tests are weird because even on main, we don't have // (lee) The two avatar tests are weird because even on main, we don't have
// anything to do with the avatar inside the onboarding test. Due to the // anything to do with the avatar inside the onboarding test. Due to the
// low impact of an avatar not showing I'm changing this to fixme. // low impact of an avatar not showing I'm changing this to fixme.
test.fixme( test.fixme(
'Avatar text updates depending on image load success', 'Avatar text updates depending on image load success',
{ async ({ context, page, homePage, tronApp }) => {
appSettings: { if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
onboarding_status: '', onboarding_status: '',
}, },
}, })
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
// Override beforeEach test setup // Override beforeEach test setup
await context.addInitScript( await context.addInitScript(
async ({ settingsKey, settings }) => { async ({ settingsKey, settings }) => {
@ -388,15 +396,16 @@ test.describe('Onboarding tests', () => {
test.fixme( test.fixme(
"Avatar text doesn't mention avatar when no avatar", "Avatar text doesn't mention avatar when no avatar",
{ async ({ context, page, homePage, tronApp }) => {
appSettings: { if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
onboarding_status: '', onboarding_status: '',
}, },
}, })
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
// Override beforeEach test setup // Override beforeEach test setup
await context.addInitScript( await context.addInitScript(
async ({ settingsKey, settings }) => { async ({ settingsKey, settings }) => {
@ -444,15 +453,17 @@ test.describe('Onboarding tests', () => {
test.fixme( test.fixme(
'Restarting onboarding on desktop takes one attempt', 'Restarting onboarding on desktop takes one attempt',
{ async ({ context, page, tronApp }) => {
appSettings: { if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
onboarding_status: 'dismissed', onboarding_status: 'dismissed',
}, },
}, })
cleanProjectDir: true,
},
async ({ context, page }) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const routerTemplateDir = join(dir, 'router-template-slate') const routerTemplateDir = join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true }) await fsp.mkdir(routerTemplateDir, { recursive: true })

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test' import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { EditorFixture } from './fixtures/editorFixture' import { EditorFixture } from './fixtures/editorFixture'
import { SceneFixture } from './fixtures/sceneFixture' import { SceneFixture } from './fixtures/sceneFixture'
import { ToolbarFixture } from './fixtures/toolbarFixture' import { ToolbarFixture } from './fixtures/toolbarFixture'

View File

@ -163,7 +163,7 @@ test(
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { .poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000, timeout: 10_000,
}) })
.toBeLessThan(15) .toBeLessThan(20)
}) })
await test.step('Clicking the logo takes us back to the projects page / home', async () => { await test.step('Clicking the logo takes us back to the projects page / home', async () => {
@ -464,7 +464,11 @@ test.describe('Can export from electron app', () => {
test( test(
`Can export using ${method}`, `Can export using ${method}`,
{ tag: ['@electron', '@skipLocalEngine'] }, { tag: ['@electron', '@skipLocalEngine'] },
async ({ context, page }, testInfo) => { async ({ context, page, tronApp }, testInfo) => {
if (!tronApp) {
fail()
}
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
@ -516,6 +520,7 @@ test.describe('Can export from electron app', () => {
storage: 'embedded', storage: 'embedded',
presentation: 'pretty', presentation: 'pretty',
}, },
tronApp.projectDirName,
page, page,
method method
) )
@ -523,7 +528,7 @@ test.describe('Can export from electron app', () => {
}) })
const filepath = path.resolve( const filepath = path.resolve(
getPlaywrightDownloadDir(page), getPlaywrightDownloadDir(tronApp.projectDirName),
'main.gltf' 'main.gltf'
) )
@ -781,6 +786,7 @@ test(
page.on('console', console.log) page.on('console', console.log)
await expect(page.getByText('router-template-slate')).toBeVisible() await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('Loading your Projects...')).not.toBeVisible()
await expect(page.getByText('Your Projects')).toBeVisible() await expect(page.getByText('Your Projects')).toBeVisible()
await page.keyboard.press('Delete') await page.keyboard.press('Delete')
@ -858,7 +864,7 @@ test.describe(`Project management commands`, () => {
test( test(
`Delete from project page`, `Delete from project page`,
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page, scene, cmdBar }, testInfo) => {
const projectName = `my_project_to_delete` const projectName = `my_project_to_delete`
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true }) await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
@ -887,6 +893,8 @@ test.describe(`Project management commands`, () => {
await projectHomeLink.click() await projectHomeLink.click()
await u.waitForPageLoad() await u.waitForPageLoad()
await scene.connectionEstablished()
await scene.settled(cmdBar)
}) })
await test.step(`Run delete command via command palette`, async () => { await test.step(`Run delete command via command palette`, async () => {
@ -909,7 +917,7 @@ test.describe(`Project management commands`, () => {
test( test(
`Rename from home page`, `Rename from home page`,
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page, homePage }, testInfo) => {
const projectName = `my_project_to_rename` const projectName = `my_project_to_rename`
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true }) await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
@ -936,6 +944,7 @@ test.describe(`Project management commands`, () => {
await test.step(`Setup`, async () => { await test.step(`Setup`, async () => {
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
page.on('console', console.log) page.on('console', console.log)
await homePage.projectsLoaded()
await expect(projectHomeLink).toBeVisible() await expect(projectHomeLink).toBeVisible()
}) })
@ -1682,7 +1691,11 @@ test(
test( test(
'You can change the root projects directory and nothing is lost', 'You can change the root projects directory and nothing is lost',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page, electronApp }, testInfo) => { async ({ context, page, tronApp, homePage }, testInfo) => {
if (!tronApp) {
fail()
}
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
await Promise.all([ await Promise.all([
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }), fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
@ -1712,6 +1725,8 @@ test(
await fsp.rm(newProjectDirName, { recursive: true }) await fsp.rm(newProjectDirName, { recursive: true })
} }
await homePage.projectsLoaded()
await test.step('We can change the root project directory', async () => { await test.step('We can change the root project directory', async () => {
// expect to see the project directory settings link // expect to see the project directory settings link
await expect( await expect(
@ -1725,7 +1740,7 @@ test(
.locator('section#projectDirectory input') .locator('section#projectDirectory input')
.inputValue() .inputValue()
const handleFile = electronApp?.evaluate( const handleFile = tronApp.electron.evaluate(
async ({ dialog }, filePaths) => { async ({ dialog }, filePaths) => {
dialog.showOpenDialog = () => dialog.showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths }) Promise.resolve({ canceled: false, filePaths })
@ -1741,6 +1756,8 @@ test(
await page.getByTestId('settings-close-button').click() await page.getByTestId('settings-close-button').click()
await homePage.projectsLoaded()
await expect(page.getByText('No Projects found')).toBeVisible() await expect(page.getByText('No Projects found')).toBeVisible()
await createProject({ name: 'project-000', page, returnHome: true }) await createProject({ name: 'project-000', page, returnHome: true })
await expect( await expect(
@ -1755,7 +1772,7 @@ test(
await page.getByTestId('project-directory-settings-link').click() await page.getByTestId('project-directory-settings-link').click()
const handleFile = electronApp?.evaluate( const handleFile = tronApp.electron.evaluate(
async ({ dialog }, filePaths) => { async ({ dialog }, filePaths) => {
dialog.showOpenDialog = () => dialog.showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths }) Promise.resolve({ canceled: false, filePaths })
@ -1767,6 +1784,7 @@ test(
await page.getByTestId('project-directory-button').click() await page.getByTestId('project-directory-button').click()
await handleFile await handleFile
await homePage.projectsLoaded()
await expect(page.locator('section#projectDirectory input')).toHaveValue( await expect(page.locator('section#projectDirectory input')).toHaveValue(
originalProjectDirName originalProjectDirName
) )
@ -2000,8 +2018,8 @@ test(
test( test(
'Settings persist across restarts', 'Settings persist across restarts',
{ tag: '@electron', cleanProjectDir: true }, { tag: '@electron' },
async ({ page }, testInfo) => { async ({ page, scene, cmdBar }, testInfo) => {
await test.step('We can change a user setting like theme', async () => { await test.step('We can change a user setting like theme', async () => {
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -2014,6 +2032,10 @@ test(
await expect(page.getByTestId('app-theme')).toHaveValue('dark') await expect(page.getByTestId('app-theme')).toHaveValue('dark')
await page.getByTestId('app-theme').selectOption('light') await page.getByTestId('app-theme').selectOption('light')
await expect(page.getByTestId('app-theme')).toHaveValue('light')
// Give time to system for writing to a persistent store
await page.waitForTimeout(1000)
}) })
await test.step('Starting the app again and we can see the same theme', async () => { await test.step('Starting the app again and we can see the same theme', async () => {

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test' import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import path from 'path' import path from 'path'
import * as fsp from 'fs/promises' import * as fsp from 'fs/promises'
import { getUtils, executorInputPath } from './test-utils' import { getUtils, executorInputPath } from './test-utils'

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test' import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
import path from 'node:path' import path from 'node:path'
import { HomePageFixture } from './fixtures/homePageFixture' import { HomePageFixture } from './fixtures/homePageFixture'
@ -2153,6 +2154,8 @@ extrude001 = extrude(profile003, length = 5)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await page.waitForTimeout(5000)
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled()
@ -2165,7 +2168,7 @@ extrude001 = extrude(profile003, length = 5)
await page.waitForTimeout(600) await page.waitForTimeout(600)
await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`) await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
await toolbar.exitSketchBtn.click() await toolbar.exitSketch()
await editor.expectEditor.not.toContain(`sketch001 = startSketchOn('XZ')`) await editor.expectEditor.not.toContain(`sketch001 = startSketchOn('XZ')`)
@ -2181,6 +2184,8 @@ extrude001 = extrude(profile003, length = 5)
)` )`
) )
await scene.settled(cmdBar)
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15) await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
}) })
}) })

View File

@ -31,8 +31,7 @@ test.beforeEach(async ({ page, context }) => {
// Help engine-manager: tear shit down. // Help engine-manager: tear shit down.
test.afterEach(async ({ page }) => { test.afterEach(async ({ page }) => {
await page.evaluate(() => { await page.evaluate(() => {
// @ts-expect-error window.engineCommandManager.tearDown()
window.tearDown()
}) })
}) })
@ -45,7 +44,11 @@ test.setTimeout(60_000)
test.skip( test.skip(
'exports of each format should work', 'exports of each format should work',
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] }, { tag: ['@snapshot', '@skipWin', '@skipMacos'] },
async ({ page, context, scene, cmdBar }) => { async ({ page, context, scene, cmdBar, tronApp }) => {
if (!tronApp) {
fail()
}
// FYI this test doesn't work with only engine running locally // FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed // And you will need to have the KittyCAD CLI installed
const u = await getUtils(page) const u = await getUtils(page)
@ -134,6 +137,7 @@ part001 = startSketchOn('-XZ')
storage: 'ascii', storage: 'ascii',
units: 'in', units: 'in',
}, },
tronApp.projectDirName,
page page
) )
) )
@ -146,6 +150,7 @@ part001 = startSketchOn('-XZ')
selection: { type: 'default_scene' }, selection: { type: 'default_scene' },
units: 'in', units: 'in',
}, },
tronApp.projectDirName,
page page
) )
) )
@ -158,6 +163,7 @@ part001 = startSketchOn('-XZ')
selection: { type: 'default_scene' }, selection: { type: 'default_scene' },
units: 'in', units: 'in',
}, },
tronApp.projectDirName,
page page
) )
) )
@ -170,6 +176,7 @@ part001 = startSketchOn('-XZ')
units: 'in', units: 'in',
selection: { type: 'default_scene' }, selection: { type: 'default_scene' },
}, },
tronApp.projectDirName,
page page
) )
) )
@ -182,6 +189,7 @@ part001 = startSketchOn('-XZ')
units: 'in', units: 'in',
selection: { type: 'default_scene' }, selection: { type: 'default_scene' },
}, },
tronApp.projectDirName,
page page
) )
) )
@ -193,6 +201,7 @@ part001 = startSketchOn('-XZ')
coords: sysType, coords: sysType,
units: 'in', units: 'in',
}, },
tronApp.projectDirName,
page page
) )
) )
@ -203,6 +212,7 @@ part001 = startSketchOn('-XZ')
storage: 'embedded', storage: 'embedded',
presentation: 'pretty', presentation: 'pretty',
}, },
tronApp.projectDirName,
page page
) )
) )
@ -213,6 +223,7 @@ part001 = startSketchOn('-XZ')
storage: 'binary', storage: 'binary',
presentation: 'pretty', presentation: 'pretty',
}, },
tronApp.projectDirName,
page page
) )
) )
@ -223,6 +234,7 @@ part001 = startSketchOn('-XZ')
storage: 'standard', storage: 'standard',
presentation: 'pretty', presentation: 'pretty',
}, },
tronApp.projectDirName,
page page
) )
) )

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -29,5 +29,5 @@
} }
} }
], ],
"kcl_version": "0.2.47" "kcl_version": "0.2.48"
} }

View File

@ -84,7 +84,7 @@ test.describe('Test network and connection issues', () => {
'Engine disconnect & reconnect in sketch mode', 'Engine disconnect & reconnect in sketch mode',
{ tag: '@skipLocalEngine' }, { tag: '@skipLocalEngine' },
async ({ page, homePage }) => { async ({ page, homePage }) => {
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit // TODO: Don't skip Mac for these. After `window.engineCommandManager.tearDown` is working in Safari, these should work on webkit
const networkToggle = page.getByTestId('network-toggle') const networkToggle = page.getByTestId('network-toggle')
const u = await getUtils(page) const u = await getUtils(page)

View File

@ -5,8 +5,9 @@ import {
_electron as electron, _electron as electron,
ElectronApplication, ElectronApplication,
Locator, Locator,
Page,
} from '@playwright/test' } from '@playwright/test'
import { test, Page } from './zoo-test' import { test } from './zoo-test'
import { EngineCommand } from 'lang/std/artifactGraph' import { EngineCommand } from 'lang/std/artifactGraph'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import fsSync from 'fs' import fsSync from 'fs'
@ -337,7 +338,7 @@ export const getMovementUtils = (opts: any) => {
async function waitForAuthAndLsp(page: Page) { async function waitForAuthAndLsp(page: Page) {
const waitForLspPromise = page.waitForEvent('console', { const waitForLspPromise = page.waitForEvent('console', {
predicate: async (message) => { predicate: async (message: any) => {
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]') // it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
// but that doesn't seem to make it to the console for macos/safari :( // but that doesn't seem to make it to the console for macos/safari :(
if (message.text().includes('start kcl lsp')) { if (message.text().includes('start kcl lsp')) {
@ -420,7 +421,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
const overlay = page.locator(locator) const overlay = page.locator(locator)
const bbox = await overlay const bbox = await overlay
.boundingBox({ timeout: 5_000 }) .boundingBox({ timeout: 5_000 })
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })) .then((box: any) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
const angle = Number(await overlay.getAttribute('data-overlay-angle')) const angle = Number(await overlay.getAttribute('data-overlay-angle'))
const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px
@ -437,7 +438,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
page page
.locator(locator) .locator(locator)
.boundingBox({ timeout: 5_000 }) .boundingBox({ timeout: 5_000 })
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })), .then((box: any) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
codeLocator: page.locator('.cm-content'), codeLocator: page.locator('.cm-content'),
crushKclCodeIntoOneLineAndThenMaybeSome: async () => { crushKclCodeIntoOneLineAndThenMaybeSome: async () => {
const code = await page.locator('.cm-content').innerText() const code = await page.locator('.cm-content').innerText()
@ -504,7 +505,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
) => { ) => {
if (cdpSession === null) { if (cdpSession === null) {
// Use a fail safe if we can't simulate disconnect (on Safari) // Use a fail safe if we can't simulate disconnect (on Safari)
return page.evaluate('window.tearDown()') return page.evaluate('window.engineCommandManager.tearDown()')
} }
return cdpSession?.send( return cdpSession?.send(
@ -631,7 +632,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
panesOpen: async (paneIds: PaneId[]) => { panesOpen: async (paneIds: PaneId[]) => {
return test?.step(`Setting ${paneIds} panes to be open`, async () => { return test?.step(`Setting ${paneIds} panes to be open`, async () => {
await page.addInitScript( await page.addInitScript(
({ PERSIST_MODELING_CONTEXT, paneIds }) => { ({ PERSIST_MODELING_CONTEXT, paneIds }: any) => {
localStorage.setItem( localStorage.setItem(
PERSIST_MODELING_CONTEXT, PERSIST_MODELING_CONTEXT,
JSON.stringify({ openPanes: paneIds }) JSON.stringify({ openPanes: paneIds })
@ -722,14 +723,14 @@ export const makeTemplate: (
const PLAYWRIGHT_DOWNLOAD_DIR = 'downloads-during-playwright' const PLAYWRIGHT_DOWNLOAD_DIR = 'downloads-during-playwright'
export const getPlaywrightDownloadDir = (page: Page) => { export const getPlaywrightDownloadDir = (rootDir: string) => {
return path.resolve(page.dir, PLAYWRIGHT_DOWNLOAD_DIR) return path.resolve(rootDir, PLAYWRIGHT_DOWNLOAD_DIR)
} }
const moveDownloadedFileTo = async (page: Page, toLocation: string) => { const moveDownloadedFileTo = async (rootDir: string, toLocation: string) => {
await fsp.mkdir(path.dirname(toLocation), { recursive: true }) await fsp.mkdir(path.dirname(toLocation), { recursive: true })
const downloadDir = getPlaywrightDownloadDir(page) const downloadDir = getPlaywrightDownloadDir(rootDir)
// Expect there to be at least one file // Expect there to be at least one file
await expect await expect
@ -756,6 +757,7 @@ export interface Paths {
export const doExport = async ( export const doExport = async (
output: Models['OutputFormat_type'], output: Models['OutputFormat_type'],
rootDir: string,
page: Page, page: Page,
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown' exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
): Promise<Paths> => { ): Promise<Paths> => {
@ -836,7 +838,7 @@ export const doExport = async (
// (declared in src/lib/exportSave) // (declared in src/lib/exportSave)
// To remain consistent with our old web tests, we want to move some downloads // To remain consistent with our old web tests, we want to move some downloads
// (images) to another directory. // (images) to another directory.
await moveDownloadedFileTo(page, downloadLocation) await moveDownloadedFileTo(rootDir, downloadLocation)
} }
return { return {
@ -859,12 +861,6 @@ export async function tearDown(page: Page, testInfo: TestInfo) {
downloadThroughput: -1, downloadThroughput: -1,
uploadThroughput: -1, uploadThroughput: -1,
}) })
// It seems it's best to give the browser about 3s to close things
// It's not super reliable but we have no real other choice for now
await page.waitForTimeout(3000)
await testInfo.tronApp?.close()
} }
// settingsOverrides may need to be augmented to take more generic items, // settingsOverrides may need to be augmented to take more generic items,
@ -936,107 +932,11 @@ let electronApp: ElectronApplication | undefined = undefined
let context: BrowserContext | undefined = undefined let context: BrowserContext | undefined = undefined
let page: Page | undefined = undefined let page: Page | undefined = undefined
export async function setupElectron({
testInfo,
cleanProjectDir = true,
appSettings,
viewport,
}: {
testInfo: TestInfo
folderSetupFn?: (projectDirName: string) => Promise<void>
cleanProjectDir?: boolean
appSettings?: DeepPartial<Settings>
viewport: {
width: number
height: number
}
}): Promise<{
electronApp: ElectronApplication
context: BrowserContext
page: Page
dir: string
}> {
// create or otherwise clear the folder
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
try {
if (fsSync.existsSync(projectDirName) && cleanProjectDir) {
await fsp.rm(projectDirName, { recursive: true })
}
} catch (e) {
console.error(e)
}
if (cleanProjectDir) {
await fsp.mkdir(projectDirName)
}
const options = {
args: ['.', '--no-sandbox'],
env: {
...process.env,
TEST_SETTINGS_FILE_KEY: projectDirName,
IS_PLAYWRIGHT: 'true',
},
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
: {}),
...(process.env.PLAYWRIGHT_RECORD_VIDEO
? {
recordVideo: {
dir: testInfo.snapshotPath(),
size: viewport,
},
}
: {}),
}
// Do this once and then reuse window on subsequent calls.
if (!electronApp) {
electronApp = await electron.launch(options)
}
if (!context || !page) {
context = electronApp.context()
page = await electronApp.firstWindow()
context.on('console', console.log)
page.on('console', console.log)
}
if (cleanProjectDir) {
const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
const settingsOverrides = settingsToToml(
appSettings
? {
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
project_directory: projectDirName,
...appSettings.app,
},
},
}
: {
settings: {
...TEST_SETTINGS,
app: {
...TEST_SETTINGS.app,
project_directory: projectDirName,
},
},
}
)
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
}
return { electronApp, page, context, dir: projectDirName }
}
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) { function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
// enabled for chrome for now // enabled for chrome for now
if (page.context().browser()?.browserType().name() === 'chromium') { if (page.context().browser()?.browserType().name() === 'chromium') {
page.on('pageerror', (exception) => { // No idea wtf exception is
page.on('pageerror', (exception: any) => {
if (isErrorWhitelisted(exception)) { if (isErrorWhitelisted(exception)) {
return return
} }

View File

@ -1127,7 +1127,6 @@ test.describe('Electron constraint tests', () => {
path.join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
}) })
const [clickHandler] = scene.makeMouseHelpers(600, 300)
await test.step('setup test', async () => { await test.step('setup test', async () => {
await homePage.expectState({ await homePage.expectState({
@ -1144,8 +1143,12 @@ test.describe('Electron constraint tests', () => {
}) })
await test.step('Double click to constrain', async () => { await test.step('Double click to constrain', async () => {
await clickHandler() // Enter sketch edit mode via feature tree
await page.getByRole('button', { name: 'Edit Sketch' }).click() await toolbar.openPane('feature-tree')
const op = await toolbar.getFeatureTreeOperation('Sketch', 0)
await op.dblclick()
await toolbar.closePane('feature-tree')
const child = page const child = page
.locator('.segment-length-label-text') .locator('.segment-length-label-text')
.first() .first()

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test' import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { deg, getUtils, wiggleMove } from './test-utils' import { deg, getUtils, wiggleMove } from './test-utils'
import { LineInputsType } from 'lang/std/sketchcombos' import { LineInputsType } from 'lang/std/sketchcombos'

View File

@ -257,6 +257,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
page, page,
homePage, homePage,
scene, scene,
cmdBar,
}) => { }) => {
test.setTimeout(90_000) test.setTimeout(90_000)
const u = await getUtils(page) const u = await getUtils(page)
@ -352,28 +353,15 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.waitForExecutionDone() await scene.settled(cmdBar)
await u.openAndClearDebugPanel() const camPosition1 = async () => {
await u.sendCustomCmd({ await scene.moveCameraTo(
type: 'modeling_cmd_req', { x: 1139.49, y: -7053, z: 8597.31 },
cmd_id: uuidv4(), { x: -2206.68, y: -1298.36, z: 60 }
cmd: { )
type: 'default_camera_look_at', }
vantage: { x: 1139.49, y: -7053, z: 8597.31 }, await camPosition1()
center: { x: -2206.68, y: -1298.36, z: 60 },
up: { x: 0, y: 0, z: 1 },
},
})
await page.waitForTimeout(100)
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await page.waitForTimeout(100)
const revolve = { x: 635, y: 253 } const revolve = { x: 635, y: 253 }
const parentExtrude = { x: 915, y: 133 } const parentExtrude = { x: 915, y: 133 }
@ -386,7 +374,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
await expect(page.locator('.cm-activeLine')).toHaveText( await expect(page.locator('.cm-activeLine')).toHaveText(
'|> line(end = [0, -pipeLength])' '|> line(end = [0, -pipeLength])'
) )
await u.clearCommandLogs() await u.openAndClearDebugPanel()
await page.keyboard.press('Delete') await page.keyboard.press('Delete')
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000) await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
await page.waitForTimeout(200) await page.waitForTimeout(200)
@ -399,11 +387,12 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
// and replace the sketch on face with a hard coded custom plane, but since there was a sketch on that plane maybe it // and replace the sketch on face with a hard coded custom plane, but since there was a sketch on that plane maybe it
// should have delete the sketch? it's broken atm, but not sure if worth fixing since desired behaviour is a little // should have delete the sketch? it's broken atm, but not sure if worth fixing since desired behaviour is a little
// vague // vague
// // DELETE PARENT EXTRUDE // DELETE PARENT EXTRUDE
// await camPosition2()
// await page.mouse.click(parentExtrude.x, parentExtrude.y) // await page.mouse.click(parentExtrude.x, parentExtrude.y)
// await page.waitForTimeout(100) // await page.waitForTimeout(100)
// await expect(page.locator('.cm-activeLine')).toHaveText( // await expect(page.locator('.cm-activeLine')).toHaveText(
// '|> line(end = [170.36, -121.61], tag = $seg01)' // '|> line(end = [112.54, 127.64], tag = $seg02)'
// ) // )
// await u.clearCommandLogs() // await u.clearCommandLogs()
// await page.keyboard.press('Backspace') // await page.keyboard.press('Backspace')
@ -463,71 +452,77 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
await page.waitForTimeout(200) await page.waitForTimeout(200)
await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet) await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet)
}) })
test.fixme( test('parent Solid should be select and deletable and uses custom planes to position children', async ({
"Deleting solid that the AST mod can't handle results in a toast message", page,
async ({ page, homePage }) => { homePage,
const u = await getUtils(page) scene,
await page.addInitScript(async () => { cmdBar,
localStorage.setItem( editor,
'persistCode', }) => {
`sketch001 = startSketchOn('XZ') test.setTimeout(90_000)
|> startProfileAt([-79.26, 95.04], %) const u = await getUtils(page)
|> line(end = [112.54, 127.64], tag = $seg02) await page.addInitScript(async () => {
|> line(end = [170.36, -121.61], tag = $seg01) localStorage.setItem(
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) 'persistCode',
|> close() `part001 = startSketchOn('XY')
extrude001 = extrude(sketch001, length = 50) yo = startProfileAt([4.83, 12.56], part001)
launderExtrudeThroughVar = extrude001 |> line(end = [15.1, 2.48])
sketch002 = startSketchOn(launderExtrudeThroughVar, seg02) |> line(end = [3.15, -9.85], tag = $seg01)
|> startProfileAt([-100.54, 16.99], %) |> line(end = [-15.17, -4.1])
|> line(end = [0, 20.03]) |> angledLine([segAng(seg01), 12.35], %, $seg02)
|> line(end = [62.61, 0], tag = $seg03) |> line(end = [-13.02, 10.03])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close()
|> close() yoo = extrude(yo, length = 4)
` sketch002 = startSketchOn(yoo, seg02)
) sketch001 = startSketchOn(yoo, 'END')
}, KCL_DEFAULT_LENGTH) profile002 = startProfileAt([-11.08, 2.39], sketch002)
await page.setBodyDimensions({ width: 1000, height: 500 }) |> line(end = [4.89, 0.9])
|> line(end = [-0.61, -2.41])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(profile002, length = 15)
profile001 = startProfileAt([7.49, 9.96], sketch001)
|> angledLine([0, 5.05], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
4.81
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
await homePage.goToModelingScene() `
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
await u.closeDebugPanel()
await u.openAndClearDebugPanel()
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
vantage: { x: 1139.49, y: -7053, z: 8597.31 },
center: { x: -2206.68, y: -1298.36, z: 60 },
up: { x: 0, y: 0, z: 1 },
},
})
await page.waitForTimeout(100)
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await page.waitForTimeout(100)
// attempt delete
await page.mouse.click(930, 139)
await page.waitForTimeout(100)
await expect(page.locator('.cm-activeLine')).toHaveText(
'|> line(end = [170.36, -121.61], tag = $seg01)'
) )
await u.clearCommandLogs() }, KCL_DEFAULT_LENGTH)
await page.keyboard.press('Delete') await page.setBodyDimensions({ width: 1000, height: 500 })
await expect(page.getByText('Unable to delete selection')).toBeVisible() await homePage.goToModelingScene()
} await scene.settled(cmdBar)
)
const extrudeWall = { x: 575, y: 238 }
// DELETE with selection on face of parent
await page.mouse.click(extrudeWall.x, extrudeWall.y)
await page.waitForTimeout(100)
await expect(page.locator('.cm-activeLine')).toHaveText(
'|> line(end = [-15.17, -4.1])'
)
await u.openAndClearDebugPanel()
await page.keyboard.press('Delete')
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
await page.waitForTimeout(200)
await editor.expectEditor.not.toContain(`yoo = extrude(yo, length = 4)`, {
shouldNormalise: true,
})
await editor.expectEditor.toContain(`startSketchOn({plane={origin`, {
shouldNormalise: true,
})
await editor.snapshot()
})
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({ test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
page, page,
homePage, homePage,

View File

@ -0,0 +1 @@
part001 = startSketchOn('XY')yo = startProfileAt([4.83, 12.56], part001) |> line(end = [15.1, 2.48]) |> line(end = [3.15, -9.85], tag = $seg01) |> line(end = [-15.17, -4.1]) |> angledLine([segAng(seg01), 12.35], %, $seg02) |> line(end = [-13.02, 10.03]) |> close()sketch002 = startSketchOn({ plane = { origin = { x = 7.49, y = 2.4, z = 0 }, xAxis = { x = -0.3, y = 0.95, z = 0 }, yAxis = { x = 0, y = 0, z = 1 }, zAxis = { x = 0.95, y = 0.3, z = 0 } }})sketch001 = startSketchOn({ plane = { origin = { x = 0, y = 0, z = 4 }, xAxis = { x = 1, y = 0, z = 0 }, yAxis = { x = 0, y = 1, z = 0 }, zAxis = { x = 0, y = 0, z = 1 } }})profile002 = startProfileAt([-11.08, 2.39], sketch002) |> line(end = [4.89, 0.9]) |> line(end = [-0.61, -2.41]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close()extrude001 = extrude(profile002, length = 15)profile001 = startProfileAt([7.49, 9.96], sketch001) |> angledLine([0, 5.05], %, $rectangleSegmentA001) |> angledLine([ segAng(rectangleSegmentA001) - 90, 4.81 ], %) |> angledLine([ segAng(rectangleSegmentA001), -segLen(rectangleSegmentA001) ], %) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close()

View File

@ -20,35 +20,40 @@ import { DeepPartial } from 'lib/types'
import { Settings } from '@rust/kcl-lib/bindings/Settings' import { Settings } from '@rust/kcl-lib/bindings/Settings'
test.describe('Testing settings', () => { test.describe('Testing settings', () => {
test( test('Stored settings are validated and fall back to defaults', async ({
'Stored settings are validated and fall back to defaults', page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
// Override beforeEach test setup // Override beforeEach test setup
// with corrupted settings // with corrupted settings
{ await tronApp.cleanProjectDir(
appSettings: TEST_SETTINGS_CORRUPTED as DeepPartial<Settings>, TEST_SETTINGS_CORRUPTED as DeepPartial<Settings>
}, )
async ({ page, homePage }) => {
await page.setBodyDimensions({ width: 1200, height: 500 })
// Check the settings were reset await page.setBodyDimensions({ width: 1200, height: 500 })
const storedSettings = tomlToSettings(
await page.evaluate( // Check the settings were reset
({ settingsKey }) => localStorage.getItem(settingsKey) || '', const storedSettings = tomlToSettings(
{ settingsKey: TEST_SETTINGS_KEY } await page.evaluate(
) ({ settingsKey }) => localStorage.getItem(settingsKey) || '',
{ settingsKey: TEST_SETTINGS_KEY }
) )
)
expect(storedSettings.settings?.app?.theme).toBe('dark') expect(storedSettings.settings?.app?.theme).toBe('dark')
// Check that the invalid settings were changed to good defaults // Check that the invalid settings were changed to good defaults
expect(storedSettings.settings?.modeling?.base_unit).toBe('in') expect(storedSettings.settings?.modeling?.base_unit).toBe('in')
expect(storedSettings.settings?.modeling?.mouse_controls).toBe('zoo') expect(storedSettings.settings?.modeling?.mouse_controls).toBe('zoo')
expect(storedSettings.settings?.app?.project_directory).toBe('') expect(storedSettings.settings?.app?.project_directory).toBe('')
expect(storedSettings.settings?.project?.default_project_name).toBe( expect(storedSettings.settings?.project?.default_project_name).toBe(
'project-$nnn' 'project-$nnn'
) )
} })
)
// The behavior is actually broken. Parent always takes precedence // The behavior is actually broken. Parent always takes precedence
test.fixme( test.fixme(
@ -357,8 +362,6 @@ test.describe('Testing settings', () => {
`Load desktop app with no settings file`, `Load desktop app with no settings file`,
{ {
tag: '@electron', tag: '@electron',
// This is what makes no settings file get created
cleanProjectDir: false,
}, },
async ({ page }, testInfo) => { async ({ page }, testInfo) => {
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -379,13 +382,17 @@ test.describe('Testing settings', () => {
`Load desktop app with a settings file, but no project directory setting`, `Load desktop app with a settings file, but no project directory setting`,
{ {
tag: '@electron', tag: '@electron',
appSettings: { },
async ({ context, page, tronApp }, testInfo) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
theme_color: '259', theme_color: '259',
}, },
}, })
},
async ({ context, page }, testInfo) => {
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
// Selectors and constants // Selectors and constants
@ -405,15 +412,20 @@ test.describe('Testing settings', () => {
'user settings reload on external change, on project and modeling view', 'user settings reload on external change, on project and modeling view',
{ {
tag: '@electron', tag: '@electron',
appSettings: { },
async ({ context, page, tronApp }, testInfo) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
// Doesn't matter what you set it to. It will // Doesn't matter what you set it to. It will
// default to 264.5 // default to 264.5
theme_color: '0', theme_color: '0',
}, },
}, })
},
async ({ context, page }, testInfo) => {
const { dir: projectDirName } = await context.folderSetupFn( const { dir: projectDirName } = await context.folderSetupFn(
async () => {} async () => {}
) )
@ -783,128 +795,136 @@ test.describe('Testing settings', () => {
}) })
}) })
test( test(`Changing system theme preferences (via media query) should update UI and stream`, async ({
`Changing system theme preferences (via media query) should update UI and stream`, page,
{ homePage,
// Override the settings so that the theme is set to `system` tronApp,
appSettings: TEST_SETTINGS_DEFAULT_THEME, }) => {
}, if (!tronApp) {
async ({ page, homePage }) => { fail()
const u = await getUtils(page)
// Selectors and constants
const darkBackgroundCss = 'oklch(0.3012 0 264.5)'
const lightBackgroundCss = 'oklch(0.9911 0 264.5)'
const darkBackgroundColor: [number, number, number] = [27, 27, 27]
const lightBackgroundColor: [number, number, number] = [245, 245, 245]
const streamBackgroundPixelIsColor = async (
color: [number, number, number]
) => {
return u.getGreatestPixDiff({ x: 1000, y: 200 }, color)
}
const toolbar = page.locator('menu').filter({ hasText: 'Start Sketch' })
await test.step(`Test setup`, async () => {
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await u.waitForPageLoad()
await page.waitForTimeout(1000)
await expect(toolbar).toBeVisible()
})
await test.step(`Check the background color is light before`, async () => {
await expect(toolbar).toHaveCSS('background-color', lightBackgroundCss)
await expect
.poll(() => streamBackgroundPixelIsColor(lightBackgroundColor))
.toBeLessThan(15)
})
await test.step(`Change media query preference to dark, emulating dusk with system theme`, async () => {
await page.emulateMedia({ colorScheme: 'dark' })
})
await test.step(`Check the background color is dark after`, async () => {
await expect(toolbar).toHaveCSS('background-color', darkBackgroundCss)
await expect
.poll(() => streamBackgroundPixelIsColor(darkBackgroundColor))
.toBeLessThan(15)
})
} }
)
test( await tronApp.cleanProjectDir({
`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, // Override the settings so that the theme is set to `system`
{ ...TEST_SETTINGS_DEFAULT_THEME,
})
const u = await getUtils(page)
// Selectors and constants
const darkBackgroundCss = 'oklch(0.3012 0 264.5)'
const lightBackgroundCss = 'oklch(0.9911 0 264.5)'
const darkBackgroundColor: [number, number, number] = [27, 27, 27]
const lightBackgroundColor: [number, number, number] = [245, 245, 245]
const streamBackgroundPixelIsColor = async (
color: [number, number, number]
) => {
return u.getGreatestPixDiff({ x: 1000, y: 200 }, color)
}
const toolbar = page.locator('menu').filter({ hasText: 'Start Sketch' })
await test.step(`Test setup`, async () => {
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await u.waitForPageLoad()
await page.waitForTimeout(1000)
await expect(toolbar).toBeVisible()
})
await test.step(`Check the background color is light before`, async () => {
await expect(toolbar).toHaveCSS('background-color', lightBackgroundCss)
await expect
.poll(() => streamBackgroundPixelIsColor(lightBackgroundColor))
.toBeLessThan(15)
})
await test.step(`Change media query preference to dark, emulating dusk with system theme`, async () => {
await page.emulateMedia({ colorScheme: 'dark' })
})
await test.step(`Check the background color is dark after`, async () => {
await expect(toolbar).toHaveCSS('background-color', darkBackgroundCss)
await expect
.poll(() => streamBackgroundPixelIsColor(darkBackgroundColor))
.toBeLessThan(15)
})
})
test(`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, async ({
context,
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
// Override beforeEach test setup // Override beforeEach test setup
// with debug panel open // with debug panel open
// but "show debug panel" set to false // but "show debug panel" set to false
appSettings: { ...TEST_SETTINGS,
...TEST_SETTINGS, app: { ...TEST_SETTINGS.app, show_debug_panel: false },
app: { ...TEST_SETTINGS.app, show_debug_panel: false }, modeling: { ...TEST_SETTINGS.modeling },
modeling: { ...TEST_SETTINGS.modeling }, })
},
},
async ({ context, page, homePage }) => {
const u = await getUtils(page)
await context.addInitScript(async () => { const u = await getUtils(page)
localStorage.setItem(
'persistModelingContext', await context.addInitScript(async () => {
'{"openPanes":["debug"]}' localStorage.setItem('persistModelingContext', '{"openPanes":["debug"]}')
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
// Constants and locators
const resizeHandle = page.locator('.sidebar-resize-handles > div.block')
const debugPaneButton = page.getByTestId('debug-pane-button')
const commandsButton = page.getByRole('button', { name: 'Commands' })
const debugPaneOption = page.getByRole('option', {
name: 'Settings · app · show debug panel',
})
async function setShowDebugPanelTo(value: 'On' | 'Off') {
await commandsButton.click()
await debugPaneOption.click()
await page.getByRole('option', { name: value }).click()
await expect(
page.getByText(
`Set show debug panel to "${value === 'On'}" for this project`
) )
}) ).toBeVisible()
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
// Constants and locators
const resizeHandle = page.locator('.sidebar-resize-handles > div.block')
const debugPaneButton = page.getByTestId('debug-pane-button')
const commandsButton = page.getByRole('button', { name: 'Commands' })
const debugPaneOption = page.getByRole('option', {
name: 'Settings · app · show debug panel',
})
async function setShowDebugPanelTo(value: 'On' | 'Off') {
await commandsButton.click()
await debugPaneOption.click()
await page.getByRole('option', { name: value }).click()
await expect(
page.getByText(
`Set show debug panel to "${value === 'On'}" for this project`
)
).toBeVisible()
}
await test.step(`Initial load with corrupted settings`, async () => {
// Check that the debug panel is not visible
await expect(debugPaneButton).not.toBeVisible()
// Check the pane resize handle wrapper is not visible
await expect(resizeHandle).not.toBeVisible()
})
await test.step(`Open code pane to verify we see the resize handles`, async () => {
await u.openKclCodePanel()
await expect(resizeHandle).toBeVisible()
await u.closeKclCodePanel()
})
await test.step(`Turn on debug panel, open it`, async () => {
await setShowDebugPanelTo('On')
await expect(debugPaneButton).toBeVisible()
// We want the logic to clear the phantom panel, so we shouldn't see
// the real panel (and therefore the resize handle) yet
await expect(resizeHandle).not.toBeVisible()
await u.openDebugPanel()
await expect(resizeHandle).toBeVisible()
})
await test.step(`Turn off debug panel setting with it open`, async () => {
await setShowDebugPanelTo('Off')
await expect(debugPaneButton).not.toBeVisible()
await expect(resizeHandle).not.toBeVisible()
})
} }
)
await test.step(`Initial load with corrupted settings`, async () => {
// Check that the debug panel is not visible
await expect(debugPaneButton).not.toBeVisible()
// Check the pane resize handle wrapper is not visible
await expect(resizeHandle).not.toBeVisible()
})
await test.step(`Open code pane to verify we see the resize handles`, async () => {
await u.openKclCodePanel()
await expect(resizeHandle).toBeVisible()
await u.closeKclCodePanel()
})
await test.step(`Turn on debug panel, open it`, async () => {
await setShowDebugPanelTo('On')
await expect(debugPaneButton).toBeVisible()
// We want the logic to clear the phantom panel, so we shouldn't see
// the real panel (and therefore the resize handle) yet
await expect(resizeHandle).not.toBeVisible()
await u.openDebugPanel()
await expect(resizeHandle).toBeVisible()
})
await test.step(`Turn off debug panel setting with it open`, async () => {
await setShowDebugPanelTo('Off')
await expect(debugPaneButton).not.toBeVisible()
await expect(resizeHandle).not.toBeVisible()
})
})
test(`Change inline units setting`, async ({ test(`Change inline units setting`, async ({
page, page,

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test' import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { getUtils, createProject } from './test-utils' import { getUtils, createProject } from './test-utils'
import { join } from 'path' import { join } from 'path'
import fs from 'fs' import fs from 'fs'

View File

@ -35,7 +35,7 @@ test.fixme('Units menu', async ({ page, homePage }) => {
test( test(
'Successful export shows a success toast', 'Successful export shows a success toast',
{ tag: '@skipLocalEngine' }, { tag: '@skipLocalEngine' },
async ({ page, homePage }) => { async ({ page, homePage, tronApp }) => {
// FYI this test doesn't work with only engine running locally // FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed // And you will need to have the KittyCAD CLI installed
const u = await getUtils(page) const u = await getUtils(page)
@ -92,12 +92,17 @@ part001 = startSketchOn('-XZ')
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
await u.clearAndCloseDebugPanel() await u.clearAndCloseDebugPanel()
if (!tronApp?.projectDirName) {
fail()
}
await doExport( await doExport(
{ {
type: 'gltf', type: 'gltf',
storage: 'embedded', storage: 'embedded',
presentation: 'pretty', presentation: 'pretty',
}, },
tronApp?.projectDirName,
page page
) )
} }
@ -465,7 +470,7 @@ test('Delete key does not navigate back', async ({ page, homePage }) => {
await expect.poll(() => page.url()).not.toContain('/settings') await expect.poll(() => page.url()).not.toContain('/settings')
}) })
test('Sketch on face', async ({ page, homePage, scene, cmdBar }) => { test('Sketch on face', async ({ page, homePage, scene, cmdBar, toolbar }) => {
test.setTimeout(90_000) test.setTimeout(90_000)
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
@ -491,25 +496,22 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.waitForExecutionDone() await scene.connectionEstablished()
await scene.settled(cmdBar)
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(300)
let previousCodeContent = await page.locator('.cm-content').innerText() let previousCodeContent = await page.locator('.cm-content').innerText()
await u.openAndClearDebugPanel() await toolbar.startSketchThenCallbackThenWaitUntilReady(async () => {
await u.doAndWaitForCmd( await u.openAndClearDebugPanel()
() => page.mouse.click(625, 165), await u.doAndWaitForCmd(
'default_camera_get_settings', () => page.mouse.click(625, 165),
true 'default_camera_get_settings',
) true
await page.waitForTimeout(150) )
await u.closeDebugPanel() await page.waitForTimeout(150)
await u.closeDebugPanel()
})
await page.waitForTimeout(300)
const firstClickPosition = [612, 238] const firstClickPosition = [612, 238]
const secondClickPosition = [661, 242] const secondClickPosition = [661, 242]

View File

@ -1,21 +1,11 @@
import { /* eslint-disable react-hooks/rules-of-hooks */
test as playwrightTestFn,
TestInfo as TestInfoPlaywright, import { test as playwrightTestFn, ElectronApplication } from '@playwright/test'
BrowserContext as BrowserContextPlaywright,
Page as PagePlaywright,
TestDetails as TestDetailsPlaywright,
PlaywrightTestArgs,
PlaywrightTestOptions,
PlaywrightWorkerArgs,
PlaywrightWorkerOptions,
ElectronApplication,
} from '@playwright/test'
import { import {
fixtures, fixturesBasedOnProcessEnvPlatform,
Fixtures, Fixtures,
AuthenticatedTronApp, ElectronZoo,
AuthenticatedApp,
} from './fixtures/fixtureSetup' } from './fixtures/fixtureSetup'
import { Settings } from '@rust/kcl-lib/bindings/Settings' import { Settings } from '@rust/kcl-lib/bindings/Settings'
@ -23,9 +13,6 @@ import { DeepPartial } from 'lib/types'
export { expect } from '@playwright/test' export { expect } from '@playwright/test'
declare module '@playwright/test' { declare module '@playwright/test' {
interface TestInfo {
tronApp?: AuthenticatedTronApp
}
interface BrowserContext { interface BrowserContext {
folderSetupFn: ( folderSetupFn: (
cb: (dir: string) => Promise<void> cb: (dir: string) => Promise<void>
@ -41,288 +28,29 @@ declare module '@playwright/test' {
} }
} }
export type TestInfo = TestInfoPlaywright // Each worker spawns a new thread, which will spawn its own ElectronZoo.
export type BrowserContext = BrowserContextPlaywright // So in some sense there is an implicit pool.
export type Page = PagePlaywright // For example, the variable just beneath this text is reused many times
export type TestDetails = TestDetailsPlaywright & { // *for one worker*.
cleanProjectDir?: boolean const electronZooInstance = new ElectronZoo()
appSettings?: DeepPartial<Settings>
}
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and // Our custom decorated Zoo test object. Makes it easier to add fixtures, and
// switch between web and electron if needed. // switch between web and electron if needed.
const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(fixtures) const playwrightTestFnWithFixtures_ = playwrightTestFn.extend<{
tronApp?: ElectronZoo
// In JavaScript you cannot replace a function's body only (despite functions }>({
// are themselves objects, which you'd expect a body property or something...) tronApp: async ({}, use, testInfo) => {
// So we must redefine the function and then re-attach properties. if (process.env.PLATFORM === 'web') {
type PWFunction = ( await use(undefined)
args: PlaywrightTestArgs & return
Fixtures &
PlaywrightWorkerArgs &
PlaywrightTestOptions &
PlaywrightWorkerOptions & {
electronApp?: ElectronApplication
},
testInfo: TestInfo
) => void | Promise<void>
let firstUrl = ''
export const test = (
desc: string,
objOrFn: PWFunction | TestDetails,
fnMaybe?: PWFunction
) => {
const hasTestConf = typeof objOrFn === 'object'
const fn = hasTestConf ? fnMaybe : objOrFn
return pwTestFnWithFixtures(
desc,
hasTestConf ? objOrFn : {},
async (
{
page,
context,
cmdBar,
editor,
toolbar,
scene,
homePage,
request,
playwright,
browser,
acceptDownloads,
bypassCSP,
colorScheme,
clientCertificates,
deviceScaleFactor,
extraHTTPHeaders,
geolocation,
hasTouch,
httpCredentials,
ignoreHTTPSErrors,
isMobile,
javaScriptEnabled,
locale,
offline,
permissions,
proxy,
storageState,
timezoneId,
userAgent,
viewport,
baseURL,
contextOptions,
actionTimeout,
navigationTimeout,
serviceWorkers,
testIdAttribute,
browserName,
defaultBrowserType,
headless,
channel,
launchOptions,
connectOptions,
screenshot,
trace,
video,
},
testInfo
) => {
// To switch to web, use PLATFORM=web environment variable.
// Only use this for debugging, since the playwright tracer is busted
// for electron.
let tronApp
if (process.env.PLATFORM === 'web') {
tronApp = new AuthenticatedApp(context, page, testInfo)
} else {
tronApp = new AuthenticatedTronApp(context, page, testInfo)
}
const fixtures: Fixtures = { cmdBar, editor, toolbar, scene, homePage }
if (tronApp instanceof AuthenticatedTronApp) {
const options = {
fixtures,
}
if (hasTestConf) {
Object.assign(options, {
appSettings: objOrFn?.appSettings,
cleanProjectDir: objOrFn?.cleanProjectDir,
})
}
await tronApp.initialise(options)
} else {
await tronApp.initialise('')
}
// We need to patch this because addInitScript will bind too late in our
// electron tests, never running. We need to call reload() after each call
// to guarantee it runs.
const oldContextAddInitScript = tronApp.context.addInitScript
tronApp.context.addInitScript = async function (a, b) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldContextAddInitScript.apply(this, [a, b])
await tronApp.page.reload()
}
// No idea why we mix and match page and context's addInitScript but we do
const oldPageAddInitScript = tronApp.page.addInitScript
tronApp.page.addInitScript = async function (a: any, b: any) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldPageAddInitScript.apply(this, [a, b])
await tronApp.page.reload()
}
// Create a consistent way to resize the page across electron and web.
// (lee) I had to do everything in the book to make electron change its
// damn window size. I succeeded in making it consistently and reliably
// do it after a whole afternoon.
tronApp.page.setBodyDimensions = async function (dims: {
width: number
height: number
}) {
await tronApp.page.setViewportSize(dims)
if (!(tronApp instanceof AuthenticatedTronApp)) {
return
}
await tronApp.electronApp?.evaluateHandle(async ({ app }, dims) => {
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
await app.resizeWindow(dims.width, dims.height)
}, dims)
return tronApp.page.evaluate(
async (dims: { width: number; height: number }) => {
await window.electron.resizeWindow(dims.width, dims.height)
window.document.body.style.width = dims.width + 'px'
window.document.body.style.height = dims.height + 'px'
window.document.documentElement.style.width = dims.width + 'px'
window.document.documentElement.style.height = dims.height + 'px'
},
dims
)
}
await tronApp.page.setBodyDimensions(tronApp.viewPortSize)
// We need to expose this in order for some tests that require folder
// creation. Before they used to do this by their own electronSetup({...})
// calls.
if (tronApp instanceof AuthenticatedTronApp) {
tronApp.context.folderSetupFn = async function (fn) {
return fn(tronApp.dir)
.then(() => tronApp.page.reload())
.then(() => ({
dir: tronApp.dir,
}))
}
}
if (!firstUrl) {
await tronApp.page.getByText('Your Projects').count()
firstUrl = tronApp.page.url()
}
// Due to the app controlling its own window context we need to inject new
// options and context here.
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
// await tronApp.electronApp.evaluate(({ app }) => {
// return app.reuseWindowForTest();
// });
await tronApp.electronApp?.evaluate(({ app }, projectDirName) => {
// @ts-ignore can't declaration merge see main.ts
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
}, tronApp.dir)
// Always start at the root view
await tronApp.page.goto(firstUrl)
// Force a hard reload, destroying the stream and other state
await tronApp.page.reload()
// tsc aint smart enough to know this'll never be undefined
// but I dont blame it, the logic to know is complex
if (fn) {
await fn(
{
context: tronApp.context,
page: tronApp.page,
electronApp:
tronApp instanceof AuthenticatedTronApp
? tronApp.electronApp
: undefined,
...fixtures,
request,
playwright,
browser,
acceptDownloads,
bypassCSP,
colorScheme,
clientCertificates,
deviceScaleFactor,
extraHTTPHeaders,
geolocation,
hasTouch,
httpCredentials,
ignoreHTTPSErrors,
isMobile,
javaScriptEnabled,
locale,
offline,
permissions,
proxy,
storageState,
timezoneId,
userAgent,
viewport,
baseURL,
contextOptions,
actionTimeout,
navigationTimeout,
serviceWorkers,
testIdAttribute,
browserName,
defaultBrowserType,
headless,
channel,
launchOptions,
connectOptions,
screenshot,
trace,
video,
},
testInfo
)
}
testInfo.tronApp =
tronApp instanceof AuthenticatedTronApp ? tronApp : undefined
} }
)
}
type ZooTest = typeof test await use(electronZooInstance)
},
})
test.describe = pwTestFnWithFixtures.describe const test = playwrightTestFnWithFixtures_.extend<Fixtures>(
test.beforeEach = pwTestFnWithFixtures.beforeEach fixturesBasedOnProcessEnvPlatform
test.afterEach = pwTestFnWithFixtures.afterEach )
test.step = pwTestFnWithFixtures.step
test.skip = pwTestFnWithFixtures.skip export { test }
test.setTimeout = pwTestFnWithFixtures.setTimeout
test.fixme = pwTestFnWithFixtures.fixme as unknown as ZooTest
test.only = pwTestFnWithFixtures.only
test.fail = pwTestFnWithFixtures.fail
test.slow = pwTestFnWithFixtures.slow
test.beforeAll = pwTestFnWithFixtures.beforeAll
test.afterAll = pwTestFnWithFixtures.afterAll
test.use = pwTestFnWithFixtures.use
test.expect = pwTestFnWithFixtures.expect
test.extend = pwTestFnWithFixtures.extend
test.info = pwTestFnWithFixtures.info

View File

@ -106,7 +106,7 @@
"files:flip-to-nightly:windows": "./scripts/flip-files-to-nightly.ps1", "files:flip-to-nightly:windows": "./scripts/flip-files-to-nightly.ps1",
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh", "files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly", "files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"postinstall": "./node_modules/.bin/electron-rebuild", "postinstall": "yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install && ./node_modules/.bin/electron-rebuild",
"make:dev": "make dev", "make:dev": "make dev",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts", "generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js", "generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -29,17 +29,17 @@ DATA;
#13 = VERTEX_POINT('NONE', #12); #13 = VERTEX_POINT('NONE', #12);
#14 = CARTESIAN_POINT('NONE', (-0.004400550000000005, 0.027303023818752777, 0.0142875)); #14 = CARTESIAN_POINT('NONE', (-0.004400550000000005, 0.027303023818752777, 0.0142875));
#15 = VERTEX_POINT('NONE', #14); #15 = VERTEX_POINT('NONE', #14);
#16 = CARTESIAN_POINT('NONE', (-0.008801100000000001, 0.034925000000000005, 0.0206375)); #16 = CARTESIAN_POINT('NONE', (-0.008801100000000003, 0.034925000000000005, 0.0206375));
#17 = VERTEX_POINT('NONE', #16); #17 = VERTEX_POINT('NONE', #16);
#18 = CARTESIAN_POINT('NONE', (-0.008801100000000001, 0.034925000000000005, 0.0142875)); #18 = CARTESIAN_POINT('NONE', (-0.008801100000000003, 0.034925000000000005, 0.0142875));
#19 = VERTEX_POINT('NONE', #18); #19 = VERTEX_POINT('NONE', #18);
#20 = CARTESIAN_POINT('NONE', (-0.00440055, 0.042546976181247226, 0.0206375)); #20 = CARTESIAN_POINT('NONE', (-0.004400550000000001, 0.042546976181247226, 0.0206375));
#21 = VERTEX_POINT('NONE', #20); #21 = VERTEX_POINT('NONE', #20);
#22 = CARTESIAN_POINT('NONE', (-0.00440055, 0.042546976181247226, 0.0142875)); #22 = CARTESIAN_POINT('NONE', (-0.004400550000000001, 0.042546976181247226, 0.0142875));
#23 = VERTEX_POINT('NONE', #22); #23 = VERTEX_POINT('NONE', #22);
#24 = CARTESIAN_POINT('NONE', (0.004400550000000001, 0.042546976181247226, 0.0206375)); #24 = CARTESIAN_POINT('NONE', (0.00440055, 0.042546976181247226, 0.0206375));
#25 = VERTEX_POINT('NONE', #24); #25 = VERTEX_POINT('NONE', #24);
#26 = CARTESIAN_POINT('NONE', (0.004400550000000001, 0.042546976181247226, 0.0142875)); #26 = CARTESIAN_POINT('NONE', (0.00440055, 0.042546976181247226, 0.0142875));
#27 = VERTEX_POINT('NONE', #26); #27 = VERTEX_POINT('NONE', #26);
#28 = CARTESIAN_POINT('NONE', (-0.0000000000000000011975821048861966, 0.039814499999999996, 0.0206375)); #28 = CARTESIAN_POINT('NONE', (-0.0000000000000000011975821048861966, 0.039814499999999996, 0.0206375));
#29 = VERTEX_POINT('NONE', #28); #29 = VERTEX_POINT('NONE', #28);
@ -57,17 +57,17 @@ DATA;
#41 = VERTEX_POINT('NONE', #40); #41 = VERTEX_POINT('NONE', #40);
#42 = CARTESIAN_POINT('NONE', (0.01860266554050596, 0.020463645441073146, 0.0142875)); #42 = CARTESIAN_POINT('NONE', (0.01860266554050596, 0.020463645441073146, 0.0142875));
#43 = VERTEX_POINT('NONE', #42); #43 = VERTEX_POINT('NONE', #42);
#44 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265653, 0.0206375)); #44 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265656, 0.0206375));
#45 = VERTEX_POINT('NONE', #44); #45 = VERTEX_POINT('NONE', #44);
#46 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265653, 0.0142875)); #46 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265656, 0.0142875));
#47 = VERTEX_POINT('NONE', #46); #47 = VERTEX_POINT('NONE', #46);
#48 = CARTESIAN_POINT('NONE', (0.030520867414747018, 0.029968094279108775, 0.0206375)); #48 = CARTESIAN_POINT('NONE', (0.030520867414747015, 0.02996809427910878, 0.0206375));
#49 = VERTEX_POINT('NONE', #48); #49 = VERTEX_POINT('NONE', #48);
#50 = CARTESIAN_POINT('NONE', (0.030520867414747018, 0.029968094279108775, 0.0142875)); #50 = CARTESIAN_POINT('NONE', (0.030520867414747015, 0.02996809427910878, 0.0142875));
#51 = VERTEX_POINT('NONE', #50); #51 = VERTEX_POINT('NONE', #50);
#52 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.0230871172187594, 0.0206375)); #52 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.023087117218759404, 0.0206375));
#53 = VERTEX_POINT('NONE', #52); #53 = VERTEX_POINT('NONE', #52);
#54 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.0230871172187594, 0.0142875)); #54 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.023087117218759404, 0.0142875));
#55 = VERTEX_POINT('NONE', #54); #55 = VERTEX_POINT('NONE', #54);
#56 = CARTESIAN_POINT('NONE', (0.03112822955872337, 0.024823934716104546, 0.0206375)); #56 = CARTESIAN_POINT('NONE', (0.03112822955872337, 0.024823934716104546, 0.0206375));
#57 = VERTEX_POINT('NONE', #56); #57 = VERTEX_POINT('NONE', #56);
@ -85,13 +85,13 @@ DATA;
#69 = VERTEX_POINT('NONE', #68); #69 = VERTEX_POINT('NONE', #68);
#70 = CARTESIAN_POINT('NONE', (0.027597694503788704, -0.0017852753360286355, 0.0142875)); #70 = CARTESIAN_POINT('NONE', (0.027597694503788704, -0.0017852753360286355, 0.0142875));
#71 = VERTEX_POINT('NONE', #70); #71 = VERTEX_POINT('NONE', #70);
#72 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.00080889442947917, 0.0206375)); #72 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.0008088944294791717, 0.0206375));
#73 = VERTEX_POINT('NONE', #72); #73 = VERTEX_POINT('NONE', #72);
#74 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.00080889442947917, 0.0142875)); #74 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.0008088944294791717, 0.0142875));
#75 = VERTEX_POINT('NONE', #74); #75 = VERTEX_POINT('NONE', #74);
#76 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.005177373852916468, 0.0206375)); #76 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.0051773738529164665, 0.0206375));
#77 = VERTEX_POINT('NONE', #76); #77 = VERTEX_POINT('NONE', #76);
#78 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.005177373852916468, 0.0142875)); #78 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.0051773738529164665, 0.0142875));
#79 = VERTEX_POINT('NONE', #78); #79 = VERTEX_POINT('NONE', #78);
#80 = CARTESIAN_POINT('NONE', (0.04050102016211167, -0.013757811900819917, 0.0206375)); #80 = CARTESIAN_POINT('NONE', (0.04050102016211167, -0.013757811900819917, 0.0206375));
#81 = VERTEX_POINT('NONE', #80); #81 = VERTEX_POINT('NONE', #80);
@ -113,17 +113,17 @@ DATA;
#97 = VERTEX_POINT('NONE', #96); #97 = VERTEX_POINT('NONE', #96);
#98 = CARTESIAN_POINT('NONE', (0.015811096615344203, -0.022689847372120698, 0.0142875)); #98 = CARTESIAN_POINT('NONE', (0.015811096615344203, -0.022689847372120698, 0.0142875));
#99 = VERTEX_POINT('NONE', #98); #99 = VERTEX_POINT('NONE', #98);
#100 = CARTESIAN_POINT('NONE', (0.023082906691976702, -0.027647683535144445, 0.0206375)); #100 = CARTESIAN_POINT('NONE', (0.023082906691976705, -0.027647683535144445, 0.0206375));
#101 = VERTEX_POINT('NONE', #100); #101 = VERTEX_POINT('NONE', #100);
#102 = CARTESIAN_POINT('NONE', (0.023082906691976702, -0.027647683535144445, 0.0142875)); #102 = CARTESIAN_POINT('NONE', (0.023082906691976705, -0.027647683535144445, 0.0142875));
#103 = VERTEX_POINT('NONE', #102); #103 = VERTEX_POINT('NONE', #102);
#104 = CARTESIAN_POINT('NONE', (0.02242519966531322, -0.03642417387451573, 0.0206375)); #104 = CARTESIAN_POINT('NONE', (0.022425199665313224, -0.03642417387451573, 0.0206375));
#105 = VERTEX_POINT('NONE', #104); #105 = VERTEX_POINT('NONE', #104);
#106 = CARTESIAN_POINT('NONE', (0.02242519966531322, -0.03642417387451573, 0.0142875)); #106 = CARTESIAN_POINT('NONE', (0.022425199665313224, -0.03642417387451573, 0.0142875));
#107 = VERTEX_POINT('NONE', #106); #107 = VERTEX_POINT('NONE', #106);
#108 = CARTESIAN_POINT('NONE', (0.014495682562017243, -0.040242828050863266, 0.0206375)); #108 = CARTESIAN_POINT('NONE', (0.014495682562017245, -0.040242828050863266, 0.0206375));
#109 = VERTEX_POINT('NONE', #108); #109 = VERTEX_POINT('NONE', #108);
#110 = CARTESIAN_POINT('NONE', (0.014495682562017243, -0.040242828050863266, 0.0142875)); #110 = CARTESIAN_POINT('NONE', (0.014495682562017245, -0.040242828050863266, 0.0142875));
#111 = VERTEX_POINT('NONE', #110); #111 = VERTEX_POINT('NONE', #110);
#112 = CARTESIAN_POINT('NONE', (0.01727486413109602, -0.03587162499110086, 0.0206375)); #112 = CARTESIAN_POINT('NONE', (0.01727486413109602, -0.03587162499110086, 0.0206375));
#113 = VERTEX_POINT('NONE', #112); #113 = VERTEX_POINT('NONE', #112);
@ -141,17 +141,17 @@ DATA;
#125 = VERTEX_POINT('NONE', #124); #125 = VERTEX_POINT('NONE', #124);
#126 = CARTESIAN_POINT('NONE', (-0.007881579512048209, -0.026508501548468247, 0.014287499999999998)); #126 = CARTESIAN_POINT('NONE', (-0.007881579512048209, -0.026508501548468247, 0.014287499999999998));
#127 = VERTEX_POINT('NONE', #126); #127 = VERTEX_POINT('NONE', #126);
#128 = CARTESIAN_POINT('NONE', (-0.0072238724853847325, -0.03528499188783953, 0.020637499999999996)); #128 = CARTESIAN_POINT('NONE', (-0.007223872485384732, -0.03528499188783953, 0.020637499999999996));
#129 = VERTEX_POINT('NONE', #128); #129 = VERTEX_POINT('NONE', #128);
#130 = CARTESIAN_POINT('NONE', (-0.0072238724853847325, -0.03528499188783953, 0.014287499999999998)); #130 = CARTESIAN_POINT('NONE', (-0.007223872485384732, -0.03528499188783953, 0.014287499999999998));
#131 = VERTEX_POINT('NONE', #130); #131 = VERTEX_POINT('NONE', #130);
#132 = CARTESIAN_POINT('NONE', (-0.014495682562017234, -0.04024282805086327, 0.020637499999999996)); #132 = CARTESIAN_POINT('NONE', (-0.014495682562017232, -0.04024282805086327, 0.020637499999999996));
#133 = VERTEX_POINT('NONE', #132); #133 = VERTEX_POINT('NONE', #132);
#134 = CARTESIAN_POINT('NONE', (-0.014495682562017234, -0.04024282805086327, 0.014287499999999998)); #134 = CARTESIAN_POINT('NONE', (-0.014495682562017232, -0.04024282805086327, 0.014287499999999998));
#135 = VERTEX_POINT('NONE', #134); #135 = VERTEX_POINT('NONE', #134);
#136 = CARTESIAN_POINT('NONE', (-0.022425199665313218, -0.036424173874515735, 0.020637499999999996)); #136 = CARTESIAN_POINT('NONE', (-0.022425199665313214, -0.036424173874515735, 0.020637499999999996));
#137 = VERTEX_POINT('NONE', #136); #137 = VERTEX_POINT('NONE', #136);
#138 = CARTESIAN_POINT('NONE', (-0.022425199665313218, -0.036424173874515735, 0.014287499999999998)); #138 = CARTESIAN_POINT('NONE', (-0.022425199665313214, -0.036424173874515735, 0.014287499999999998));
#139 = VERTEX_POINT('NONE', #138); #139 = VERTEX_POINT('NONE', #138);
#140 = CARTESIAN_POINT('NONE', (-0.01727486413109601, -0.035871624991100866, 0.020637499999999996)); #140 = CARTESIAN_POINT('NONE', (-0.01727486413109601, -0.035871624991100866, 0.020637499999999996));
#141 = VERTEX_POINT('NONE', #140); #141 = VERTEX_POINT('NONE', #140);
@ -173,13 +173,13 @@ DATA;
#157 = VERTEX_POINT('NONE', #156); #157 = VERTEX_POINT('NONE', #156);
#158 = CARTESIAN_POINT('NONE', (-0.032090928341107286, -0.01635198166632771, 0.0142875)); #158 = CARTESIAN_POINT('NONE', (-0.032090928341107286, -0.01635198166632771, 0.0142875));
#159 = VERTEX_POINT('NONE', #158); #159 = VERTEX_POINT('NONE', #158);
#160 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.01375781190081989, 0.0206375)); #160 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.013757811900819893, 0.0206375));
#161 = VERTEX_POINT('NONE', #160); #161 = VERTEX_POINT('NONE', #160);
#162 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.01375781190081989, 0.0142875)); #162 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.013757811900819893, 0.0142875));
#163 = VERTEX_POINT('NONE', #162); #163 = VERTEX_POINT('NONE', #162);
#164 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.005177373852916439, 0.0206375)); #164 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.0051773738529164405, 0.0206375));
#165 = VERTEX_POINT('NONE', #164); #165 = VERTEX_POINT('NONE', #164);
#166 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.005177373852916439, 0.0142875)); #166 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.0051773738529164405, 0.0142875));
#167 = VERTEX_POINT('NONE', #166); #167 = VERTEX_POINT('NONE', #166);
#168 = CARTESIAN_POINT('NONE', (-0.038816267359563214, -0.008859559725003652, 0.0206375)); #168 = CARTESIAN_POINT('NONE', (-0.038816267359563214, -0.008859559725003652, 0.0206375));
#169 = VERTEX_POINT('NONE', #168); #169 = VERTEX_POINT('NONE', #168);
@ -201,9 +201,9 @@ DATA;
#185 = VERTEX_POINT('NONE', #184); #185 = VERTEX_POINT('NONE', #184);
#186 = CARTESIAN_POINT('NONE', (-0.032792860620334846, 0.014894404269566884, 0.0142875)); #186 = CARTESIAN_POINT('NONE', (-0.032792860620334846, 0.014894404269566884, 0.0142875));
#187 = VERTEX_POINT('NONE', #186); #187 = VERTEX_POINT('NONE', #186);
#188 = CARTESIAN_POINT('NONE', (-0.036008263509885924, 0.02308711721875939, 0.0206375)); #188 = CARTESIAN_POINT('NONE', (-0.03600826350988593, 0.02308711721875939, 0.0206375));
#189 = VERTEX_POINT('NONE', #188); #189 = VERTEX_POINT('NONE', #188);
#190 = CARTESIAN_POINT('NONE', (-0.036008263509885924, 0.02308711721875939, 0.0142875)); #190 = CARTESIAN_POINT('NONE', (-0.03600826350988593, 0.02308711721875939, 0.0142875));
#191 = VERTEX_POINT('NONE', #190); #191 = VERTEX_POINT('NONE', #190);
#192 = CARTESIAN_POINT('NONE', (-0.03052086741474703, 0.029968094279108768, 0.0206375)); #192 = CARTESIAN_POINT('NONE', (-0.03052086741474703, 0.029968094279108768, 0.0206375));
#193 = VERTEX_POINT('NONE', #192); #193 = VERTEX_POINT('NONE', #192);
@ -715,49 +715,49 @@ DATA;
#699 = VECTOR('NONE', #698, 1); #699 = VECTOR('NONE', #698, 1);
#700 = CARTESIAN_POINT('NONE', (0.004400549999999997, 0.027303023818752777, 0.0142875)); #700 = CARTESIAN_POINT('NONE', (0.004400549999999997, 0.027303023818752777, 0.0142875));
#701 = LINE('NONE', #700, #699); #701 = LINE('NONE', #700, #699);
#702 = DIRECTION('NONE', (-0.49999999999999944, 0.866025403784439, 0)); #702 = DIRECTION('NONE', (-0.49999999999999956, 0.8660254037844389, 0));
#703 = VECTOR('NONE', #702, 1); #703 = VECTOR('NONE', #702, 1);
#704 = CARTESIAN_POINT('NONE', (-0.004400550000000005, 0.027303023818752777, 0.0206375)); #704 = CARTESIAN_POINT('NONE', (-0.004400550000000005, 0.027303023818752777, 0.0206375));
#705 = LINE('NONE', #704, #703); #705 = LINE('NONE', #704, #703);
#706 = DIRECTION('NONE', (0, 0, -1)); #706 = DIRECTION('NONE', (0, 0, -1));
#707 = VECTOR('NONE', #706, 1); #707 = VECTOR('NONE', #706, 1);
#708 = CARTESIAN_POINT('NONE', (-0.008801100000000001, 0.034925000000000005, 0.0206375)); #708 = CARTESIAN_POINT('NONE', (-0.008801100000000003, 0.034925000000000005, 0.0206375));
#709 = LINE('NONE', #708, #707); #709 = LINE('NONE', #708, #707);
#710 = DIRECTION('NONE', (-0.49999999999999944, 0.866025403784439, 0)); #710 = DIRECTION('NONE', (-0.49999999999999956, 0.8660254037844389, 0));
#711 = VECTOR('NONE', #710, 1); #711 = VECTOR('NONE', #710, 1);
#712 = CARTESIAN_POINT('NONE', (-0.004400550000000005, 0.027303023818752777, 0.0142875)); #712 = CARTESIAN_POINT('NONE', (-0.004400550000000005, 0.027303023818752777, 0.0142875));
#713 = LINE('NONE', #712, #711); #713 = LINE('NONE', #712, #711);
#714 = DIRECTION('NONE', (0.5000000000000002, 0.8660254037844386, 0)); #714 = DIRECTION('NONE', (0.5000000000000002, 0.8660254037844386, 0));
#715 = VECTOR('NONE', #714, 1); #715 = VECTOR('NONE', #714, 1);
#716 = CARTESIAN_POINT('NONE', (-0.008801100000000001, 0.034925000000000005, 0.0206375)); #716 = CARTESIAN_POINT('NONE', (-0.008801100000000003, 0.034925000000000005, 0.0206375));
#717 = LINE('NONE', #716, #715); #717 = LINE('NONE', #716, #715);
#718 = DIRECTION('NONE', (0, 0, -1)); #718 = DIRECTION('NONE', (0, 0, -1));
#719 = VECTOR('NONE', #718, 1); #719 = VECTOR('NONE', #718, 1);
#720 = CARTESIAN_POINT('NONE', (-0.00440055, 0.042546976181247226, 0.0206375)); #720 = CARTESIAN_POINT('NONE', (-0.004400550000000001, 0.042546976181247226, 0.0206375));
#721 = LINE('NONE', #720, #719); #721 = LINE('NONE', #720, #719);
#722 = DIRECTION('NONE', (0.5000000000000002, 0.8660254037844386, 0)); #722 = DIRECTION('NONE', (0.5000000000000002, 0.8660254037844386, 0));
#723 = VECTOR('NONE', #722, 1); #723 = VECTOR('NONE', #722, 1);
#724 = CARTESIAN_POINT('NONE', (-0.008801100000000001, 0.034925000000000005, 0.0142875)); #724 = CARTESIAN_POINT('NONE', (-0.008801100000000003, 0.034925000000000005, 0.0142875));
#725 = LINE('NONE', #724, #723); #725 = LINE('NONE', #724, #723);
#726 = DIRECTION('NONE', (1, 0, 0)); #726 = DIRECTION('NONE', (1, 0, 0));
#727 = VECTOR('NONE', #726, 1); #727 = VECTOR('NONE', #726, 1);
#728 = CARTESIAN_POINT('NONE', (-0.00440055, 0.042546976181247226, 0.0206375)); #728 = CARTESIAN_POINT('NONE', (-0.004400550000000001, 0.042546976181247226, 0.0206375));
#729 = LINE('NONE', #728, #727); #729 = LINE('NONE', #728, #727);
#730 = DIRECTION('NONE', (0, 0, -1)); #730 = DIRECTION('NONE', (0, 0, -1));
#731 = VECTOR('NONE', #730, 1); #731 = VECTOR('NONE', #730, 1);
#732 = CARTESIAN_POINT('NONE', (0.004400550000000001, 0.042546976181247226, 0.0206375)); #732 = CARTESIAN_POINT('NONE', (0.00440055, 0.042546976181247226, 0.0206375));
#733 = LINE('NONE', #732, #731); #733 = LINE('NONE', #732, #731);
#734 = DIRECTION('NONE', (1, 0, 0)); #734 = DIRECTION('NONE', (1, 0, 0));
#735 = VECTOR('NONE', #734, 1); #735 = VECTOR('NONE', #734, 1);
#736 = CARTESIAN_POINT('NONE', (-0.00440055, 0.042546976181247226, 0.0142875)); #736 = CARTESIAN_POINT('NONE', (-0.004400550000000001, 0.042546976181247226, 0.0142875));
#737 = LINE('NONE', #736, #735); #737 = LINE('NONE', #736, #735);
#738 = DIRECTION('NONE', (0.4999999999999997, -0.8660254037844388, 0)); #738 = DIRECTION('NONE', (0.4999999999999999, -0.8660254037844388, 0));
#739 = VECTOR('NONE', #738, 1); #739 = VECTOR('NONE', #738, 1);
#740 = CARTESIAN_POINT('NONE', (0.004400550000000001, 0.042546976181247226, 0.0206375)); #740 = CARTESIAN_POINT('NONE', (0.00440055, 0.042546976181247226, 0.0206375));
#741 = LINE('NONE', #740, #739); #741 = LINE('NONE', #740, #739);
#742 = DIRECTION('NONE', (0.4999999999999997, -0.8660254037844388, 0)); #742 = DIRECTION('NONE', (0.4999999999999999, -0.8660254037844388, 0));
#743 = VECTOR('NONE', #742, 1); #743 = VECTOR('NONE', #742, 1);
#744 = CARTESIAN_POINT('NONE', (0.004400550000000001, 0.042546976181247226, 0.0142875)); #744 = CARTESIAN_POINT('NONE', (0.00440055, 0.042546976181247226, 0.0142875));
#745 = LINE('NONE', #744, #743); #745 = LINE('NONE', #744, #743);
#746 = DIRECTION('NONE', (-0.0000000000000002449293598294707, 1, -0)); #746 = DIRECTION('NONE', (-0.0000000000000002449293598294707, 1, -0));
#747 = DIRECTION('NONE', (-0, 0, 1)); #747 = DIRECTION('NONE', (-0, 0, 1));
@ -801,49 +801,49 @@ DATA;
#785 = VECTOR('NONE', #784, 1); #785 = VECTOR('NONE', #784, 1);
#786 = CARTESIAN_POINT('NONE', (0.024090061635644863, 0.013582668380723768, 0.0142875)); #786 = CARTESIAN_POINT('NONE', (0.024090061635644863, 0.013582668380723768, 0.0142875));
#787 = LINE('NONE', #786, #785); #787 = LINE('NONE', #786, #785);
#788 = DIRECTION('NONE', (0.3653410243663952, 0.9308737486442042, 0)); #788 = DIRECTION('NONE', (0.3653410243663951, 0.9308737486442044, 0));
#789 = VECTOR('NONE', #788, 1); #789 = VECTOR('NONE', #788, 1);
#790 = CARTESIAN_POINT('NONE', (0.01860266554050596, 0.020463645441073146, 0.0206375)); #790 = CARTESIAN_POINT('NONE', (0.01860266554050596, 0.020463645441073146, 0.0206375));
#791 = LINE('NONE', #790, #789); #791 = LINE('NONE', #790, #789);
#792 = DIRECTION('NONE', (0, 0, -1)); #792 = DIRECTION('NONE', (0, 0, -1));
#793 = VECTOR('NONE', #792, 1); #793 = VECTOR('NONE', #792, 1);
#794 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265653, 0.0206375)); #794 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265656, 0.0206375));
#795 = LINE('NONE', #794, #793); #795 = LINE('NONE', #794, #793);
#796 = DIRECTION('NONE', (0.3653410243663952, 0.9308737486442042, 0)); #796 = DIRECTION('NONE', (0.3653410243663951, 0.9308737486442044, 0));
#797 = VECTOR('NONE', #796, 1); #797 = VECTOR('NONE', #796, 1);
#798 = CARTESIAN_POINT('NONE', (0.01860266554050596, 0.020463645441073146, 0.0142875)); #798 = CARTESIAN_POINT('NONE', (0.01860266554050596, 0.020463645441073146, 0.0142875));
#799 = LINE('NONE', #798, #797); #799 = LINE('NONE', #798, #797);
#800 = DIRECTION('NONE', (0.9888308262251287, 0.14904226617617372, 0)); #800 = DIRECTION('NONE', (0.9888308262251287, 0.1490422661761738, 0));
#801 = VECTOR('NONE', #800, 1); #801 = VECTOR('NONE', #800, 1);
#802 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265653, 0.0206375)); #802 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265656, 0.0206375));
#803 = LINE('NONE', #802, #801); #803 = LINE('NONE', #802, #801);
#804 = DIRECTION('NONE', (0, 0, -1)); #804 = DIRECTION('NONE', (0, 0, -1));
#805 = VECTOR('NONE', #804, 1); #805 = VECTOR('NONE', #804, 1);
#806 = CARTESIAN_POINT('NONE', (0.030520867414747018, 0.029968094279108775, 0.0206375)); #806 = CARTESIAN_POINT('NONE', (0.030520867414747015, 0.02996809427910878, 0.0206375));
#807 = LINE('NONE', #806, #805); #807 = LINE('NONE', #806, #805);
#808 = DIRECTION('NONE', (0.9888308262251287, 0.14904226617617372, 0)); #808 = DIRECTION('NONE', (0.9888308262251287, 0.1490422661761738, 0));
#809 = VECTOR('NONE', #808, 1); #809 = VECTOR('NONE', #808, 1);
#810 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265653, 0.0142875)); #810 = CARTESIAN_POINT('NONE', (0.02181806843005704, 0.028656358390265656, 0.0142875));
#811 = LINE('NONE', #810, #809); #811 = LINE('NONE', #810, #809);
#812 = DIRECTION('NONE', (0.6234898018587341, -0.7818314824680294, 0)); #812 = DIRECTION('NONE', (0.6234898018587344, -0.7818314824680292, 0));
#813 = VECTOR('NONE', #812, 1); #813 = VECTOR('NONE', #812, 1);
#814 = CARTESIAN_POINT('NONE', (0.030520867414747018, 0.029968094279108775, 0.0206375)); #814 = CARTESIAN_POINT('NONE', (0.030520867414747015, 0.02996809427910878, 0.0206375));
#815 = LINE('NONE', #814, #813); #815 = LINE('NONE', #814, #813);
#816 = DIRECTION('NONE', (0, 0, -1)); #816 = DIRECTION('NONE', (0, 0, -1));
#817 = VECTOR('NONE', #816, 1); #817 = VECTOR('NONE', #816, 1);
#818 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.0230871172187594, 0.0206375)); #818 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.023087117218759404, 0.0206375));
#819 = LINE('NONE', #818, #817); #819 = LINE('NONE', #818, #817);
#820 = DIRECTION('NONE', (0.6234898018587341, -0.7818314824680294, 0)); #820 = DIRECTION('NONE', (0.6234898018587344, -0.7818314824680292, 0));
#821 = VECTOR('NONE', #820, 1); #821 = VECTOR('NONE', #820, 1);
#822 = CARTESIAN_POINT('NONE', (0.030520867414747018, 0.029968094279108775, 0.0142875)); #822 = CARTESIAN_POINT('NONE', (0.030520867414747015, 0.02996809427910878, 0.0142875));
#823 = LINE('NONE', #822, #821); #823 = LINE('NONE', #822, #821);
#824 = DIRECTION('NONE', (-0.36534102436639604, -0.9308737486442038, 0)); #824 = DIRECTION('NONE', (-0.36534102436639593, -0.9308737486442038, 0));
#825 = VECTOR('NONE', #824, 1); #825 = VECTOR('NONE', #824, 1);
#826 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.0230871172187594, 0.0206375)); #826 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.023087117218759404, 0.0206375));
#827 = LINE('NONE', #826, #825); #827 = LINE('NONE', #826, #825);
#828 = DIRECTION('NONE', (-0.36534102436639604, -0.9308737486442038, 0)); #828 = DIRECTION('NONE', (-0.36534102436639593, -0.9308737486442038, 0));
#829 = VECTOR('NONE', #828, 1); #829 = VECTOR('NONE', #828, 1);
#830 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.0230871172187594, 0.0142875)); #830 = CARTESIAN_POINT('NONE', (0.036008263509885924, 0.023087117218759404, 0.0142875));
#831 = LINE('NONE', #830, #829); #831 = LINE('NONE', #830, #829);
#832 = DIRECTION('NONE', (0.78183148246803, 0.6234898018587336, -0)); #832 = DIRECTION('NONE', (0.78183148246803, 0.6234898018587336, -0));
#833 = DIRECTION('NONE', (0, 0, 1.0000000000000002)); #833 = DIRECTION('NONE', (0, 0, 1.0000000000000002));
@ -887,41 +887,41 @@ DATA;
#871 = VECTOR('NONE', #870, 1); #871 = VECTOR('NONE', #870, 1);
#872 = CARTESIAN_POINT('NONE', (0.025639265511945786, -0.010365713383932086, 0.0142875)); #872 = CARTESIAN_POINT('NONE', (0.025639265511945786, -0.010365713383932086, 0.0142875));
#873 = LINE('NONE', #872, #871); #873 = LINE('NONE', #872, #871);
#874 = DIRECTION('NONE', (0.955572805786141, 0.2947551744109036, 0)); #874 = DIRECTION('NONE', (0.9555728057861409, 0.29475517441090376, 0));
#875 = VECTOR('NONE', #874, 1); #875 = VECTOR('NONE', #874, 1);
#876 = CARTESIAN_POINT('NONE', (0.027597694503788704, -0.0017852753360286355, 0.0206375)); #876 = CARTESIAN_POINT('NONE', (0.027597694503788704, -0.0017852753360286355, 0.0206375));
#877 = LINE('NONE', #876, #875); #877 = LINE('NONE', #876, #875);
#878 = DIRECTION('NONE', (0, 0, -1)); #878 = DIRECTION('NONE', (0, 0, -1));
#879 = VECTOR('NONE', #878, 1); #879 = VECTOR('NONE', #878, 1);
#880 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.00080889442947917, 0.0206375)); #880 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.0008088944294791717, 0.0206375));
#881 = LINE('NONE', #880, #879); #881 = LINE('NONE', #880, #879);
#882 = DIRECTION('NONE', (0.955572805786141, 0.2947551744109036, 0)); #882 = DIRECTION('NONE', (0.9555728057861409, 0.29475517441090376, 0));
#883 = VECTOR('NONE', #882, 1); #883 = VECTOR('NONE', #882, 1);
#884 = CARTESIAN_POINT('NONE', (0.027597694503788704, -0.0017852753360286355, 0.0142875)); #884 = CARTESIAN_POINT('NONE', (0.027597694503788704, -0.0017852753360286355, 0.0142875));
#885 = LINE('NONE', #884, #883); #885 = LINE('NONE', #884, #883);
#886 = DIRECTION('NONE', (0.7330518718298261, -0.6801727377709197, 0)); #886 = DIRECTION('NONE', (0.7330518718298261, -0.6801727377709197, 0));
#887 = VECTOR('NONE', #886, 1); #887 = VECTOR('NONE', #886, 1);
#888 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.00080889442947917, 0.0206375)); #888 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.0008088944294791717, 0.0206375));
#889 = LINE('NONE', #888, #887); #889 = LINE('NONE', #888, #887);
#890 = DIRECTION('NONE', (0, 0, -1)); #890 = DIRECTION('NONE', (0, 0, -1));
#891 = VECTOR('NONE', #890, 1); #891 = VECTOR('NONE', #890, 1);
#892 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.005177373852916468, 0.0206375)); #892 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.0051773738529164665, 0.0206375));
#893 = LINE('NONE', #892, #891); #893 = LINE('NONE', #892, #891);
#894 = DIRECTION('NONE', (0.7330518718298261, -0.6801727377709197, 0)); #894 = DIRECTION('NONE', (0.7330518718298261, -0.6801727377709197, 0));
#895 = VECTOR('NONE', #894, 1); #895 = VECTOR('NONE', #894, 1);
#896 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.00080889442947917, 0.0142875)); #896 = CARTESIAN_POINT('NONE', (0.036007786324793116, 0.0008088944294791717, 0.0142875));
#897 = LINE('NONE', #896, #895); #897 = LINE('NONE', #896, #895);
#898 = DIRECTION('NONE', (-0.22252093395631514, -0.9749279121818235, 0)); #898 = DIRECTION('NONE', (-0.22252093395631506, -0.9749279121818235, 0));
#899 = VECTOR('NONE', #898, 1); #899 = VECTOR('NONE', #898, 1);
#900 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.005177373852916468, 0.0206375)); #900 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.0051773738529164665, 0.0206375));
#901 = LINE('NONE', #900, #899); #901 = LINE('NONE', #900, #899);
#902 = DIRECTION('NONE', (0, 0, -1)); #902 = DIRECTION('NONE', (0, 0, -1));
#903 = VECTOR('NONE', #902, 1); #903 = VECTOR('NONE', #902, 1);
#904 = CARTESIAN_POINT('NONE', (0.04050102016211167, -0.013757811900819917, 0.0206375)); #904 = CARTESIAN_POINT('NONE', (0.04050102016211167, -0.013757811900819917, 0.0206375));
#905 = LINE('NONE', #904, #903); #905 = LINE('NONE', #904, #903);
#906 = DIRECTION('NONE', (-0.22252093395631514, -0.9749279121818235, 0)); #906 = DIRECTION('NONE', (-0.22252093395631506, -0.9749279121818235, 0));
#907 = VECTOR('NONE', #906, 1); #907 = VECTOR('NONE', #906, 1);
#908 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.005177373852916468, 0.0142875)); #908 = CARTESIAN_POINT('NONE', (0.042459449153954595, -0.0051773738529164665, 0.0142875));
#909 = LINE('NONE', #908, #907); #909 = LINE('NONE', #908, #907);
#910 = DIRECTION('NONE', (-0.9555728057861407, -0.2947551744109045, 0)); #910 = DIRECTION('NONE', (-0.9555728057861407, -0.2947551744109045, 0));
#911 = VECTOR('NONE', #910, 1); #911 = VECTOR('NONE', #910, 1);
@ -973,49 +973,49 @@ DATA;
#957 = VECTOR('NONE', #956, 1); #957 = VECTOR('NONE', #956, 1);
#958 = CARTESIAN_POINT('NONE', (0.007881579512048221, -0.02650850154846824, 0.0142875)); #958 = CARTESIAN_POINT('NONE', (0.007881579512048221, -0.02650850154846824, 0.0142875));
#959 = LINE('NONE', #958, #957); #959 = LINE('NONE', #958, #957);
#960 = DIRECTION('NONE', (0.8262387743159946, -0.5633200580636225, 0)); #960 = DIRECTION('NONE', (0.8262387743159947, -0.5633200580636224, 0));
#961 = VECTOR('NONE', #960, 1); #961 = VECTOR('NONE', #960, 1);
#962 = CARTESIAN_POINT('NONE', (0.015811096615344203, -0.022689847372120698, 0.0206375)); #962 = CARTESIAN_POINT('NONE', (0.015811096615344203, -0.022689847372120698, 0.0206375));
#963 = LINE('NONE', #962, #961); #963 = LINE('NONE', #962, #961);
#964 = DIRECTION('NONE', (0, 0, -1)); #964 = DIRECTION('NONE', (0, 0, -1));
#965 = VECTOR('NONE', #964, 1); #965 = VECTOR('NONE', #964, 1);
#966 = CARTESIAN_POINT('NONE', (0.023082906691976702, -0.027647683535144445, 0.0206375)); #966 = CARTESIAN_POINT('NONE', (0.023082906691976705, -0.027647683535144445, 0.0206375));
#967 = LINE('NONE', #966, #965); #967 = LINE('NONE', #966, #965);
#968 = DIRECTION('NONE', (0.8262387743159946, -0.5633200580636225, 0)); #968 = DIRECTION('NONE', (0.8262387743159947, -0.5633200580636224, 0));
#969 = VECTOR('NONE', #968, 1); #969 = VECTOR('NONE', #968, 1);
#970 = CARTESIAN_POINT('NONE', (0.015811096615344203, -0.022689847372120698, 0.0142875)); #970 = CARTESIAN_POINT('NONE', (0.015811096615344203, -0.022689847372120698, 0.0142875));
#971 = LINE('NONE', #970, #969); #971 = LINE('NONE', #970, #969);
#972 = DIRECTION('NONE', (-0.07473009358642455, -0.9972037971811801, 0)); #972 = DIRECTION('NONE', (-0.07473009358642455, -0.9972037971811801, 0));
#973 = VECTOR('NONE', #972, 1); #973 = VECTOR('NONE', #972, 1);
#974 = CARTESIAN_POINT('NONE', (0.023082906691976702, -0.027647683535144445, 0.0206375)); #974 = CARTESIAN_POINT('NONE', (0.023082906691976705, -0.027647683535144445, 0.0206375));
#975 = LINE('NONE', #974, #973); #975 = LINE('NONE', #974, #973);
#976 = DIRECTION('NONE', (0, 0, -1)); #976 = DIRECTION('NONE', (0, 0, -1));
#977 = VECTOR('NONE', #976, 1); #977 = VECTOR('NONE', #976, 1);
#978 = CARTESIAN_POINT('NONE', (0.02242519966531322, -0.03642417387451573, 0.0206375)); #978 = CARTESIAN_POINT('NONE', (0.022425199665313224, -0.03642417387451573, 0.0206375));
#979 = LINE('NONE', #978, #977); #979 = LINE('NONE', #978, #977);
#980 = DIRECTION('NONE', (-0.07473009358642455, -0.9972037971811801, 0)); #980 = DIRECTION('NONE', (-0.07473009358642455, -0.9972037971811801, 0));
#981 = VECTOR('NONE', #980, 1); #981 = VECTOR('NONE', #980, 1);
#982 = CARTESIAN_POINT('NONE', (0.023082906691976702, -0.027647683535144445, 0.0142875)); #982 = CARTESIAN_POINT('NONE', (0.023082906691976705, -0.027647683535144445, 0.0142875));
#983 = LINE('NONE', #982, #981); #983 = LINE('NONE', #982, #981);
#984 = DIRECTION('NONE', (-0.9009688679024193, -0.43388373911755806, 0)); #984 = DIRECTION('NONE', (-0.9009688679024193, -0.43388373911755795, 0));
#985 = VECTOR('NONE', #984, 1); #985 = VECTOR('NONE', #984, 1);
#986 = CARTESIAN_POINT('NONE', (0.02242519966531322, -0.03642417387451573, 0.0206375)); #986 = CARTESIAN_POINT('NONE', (0.022425199665313224, -0.03642417387451573, 0.0206375));
#987 = LINE('NONE', #986, #985); #987 = LINE('NONE', #986, #985);
#988 = DIRECTION('NONE', (0, 0, -1)); #988 = DIRECTION('NONE', (0, 0, -1));
#989 = VECTOR('NONE', #988, 1); #989 = VECTOR('NONE', #988, 1);
#990 = CARTESIAN_POINT('NONE', (0.014495682562017243, -0.040242828050863266, 0.0206375)); #990 = CARTESIAN_POINT('NONE', (0.014495682562017245, -0.040242828050863266, 0.0206375));
#991 = LINE('NONE', #990, #989); #991 = LINE('NONE', #990, #989);
#992 = DIRECTION('NONE', (-0.9009688679024193, -0.43388373911755806, 0)); #992 = DIRECTION('NONE', (-0.9009688679024193, -0.43388373911755795, 0));
#993 = VECTOR('NONE', #992, 1); #993 = VECTOR('NONE', #992, 1);
#994 = CARTESIAN_POINT('NONE', (0.02242519966531322, -0.03642417387451573, 0.0142875)); #994 = CARTESIAN_POINT('NONE', (0.022425199665313224, -0.03642417387451573, 0.0142875));
#995 = LINE('NONE', #994, #993); #995 = LINE('NONE', #994, #993);
#996 = DIRECTION('NONE', (-0.8262387743159952, 0.5633200580636217, 0)); #996 = DIRECTION('NONE', (-0.8262387743159951, 0.5633200580636216, 0));
#997 = VECTOR('NONE', #996, 1); #997 = VECTOR('NONE', #996, 1);
#998 = CARTESIAN_POINT('NONE', (0.014495682562017243, -0.040242828050863266, 0.0206375)); #998 = CARTESIAN_POINT('NONE', (0.014495682562017245, -0.040242828050863266, 0.0206375));
#999 = LINE('NONE', #998, #997); #999 = LINE('NONE', #998, #997);
#1000 = DIRECTION('NONE', (-0.8262387743159952, 0.5633200580636217, 0)); #1000 = DIRECTION('NONE', (-0.8262387743159951, 0.5633200580636216, 0));
#1001 = VECTOR('NONE', #1000, 1); #1001 = VECTOR('NONE', #1000, 1);
#1002 = CARTESIAN_POINT('NONE', (0.014495682562017243, -0.040242828050863266, 0.0142875)); #1002 = CARTESIAN_POINT('NONE', (0.014495682562017245, -0.040242828050863266, 0.0142875));
#1003 = LINE('NONE', #1002, #1001); #1003 = LINE('NONE', #1002, #1001);
#1004 = DIRECTION('NONE', (0.4338837391175578, -0.9009688679024195, -0)); #1004 = DIRECTION('NONE', (0.4338837391175578, -0.9009688679024195, -0));
#1005 = DIRECTION('NONE', (0, 0, 1.0000000000000002)); #1005 = DIRECTION('NONE', (0, 0, 1.0000000000000002));
@ -1059,49 +1059,49 @@ DATA;
#1043 = VECTOR('NONE', #1042, 1); #1043 = VECTOR('NONE', #1042, 1);
#1044 = CARTESIAN_POINT('NONE', (-0.015811096615344192, -0.022689847372120705, 0.014287499999999998)); #1044 = CARTESIAN_POINT('NONE', (-0.015811096615344192, -0.022689847372120705, 0.014287499999999998));
#1045 = LINE('NONE', #1044, #1043); #1045 = LINE('NONE', #1044, #1043);
#1046 = DIRECTION('NONE', (0.07473009358642402, -0.9972037971811802, 0)); #1046 = DIRECTION('NONE', (0.07473009358642413, -0.9972037971811802, 0));
#1047 = VECTOR('NONE', #1046, 1); #1047 = VECTOR('NONE', #1046, 1);
#1048 = CARTESIAN_POINT('NONE', (-0.007881579512048209, -0.026508501548468247, 0.020637499999999996)); #1048 = CARTESIAN_POINT('NONE', (-0.007881579512048209, -0.026508501548468247, 0.020637499999999996));
#1049 = LINE('NONE', #1048, #1047); #1049 = LINE('NONE', #1048, #1047);
#1050 = DIRECTION('NONE', (0, 0, -1)); #1050 = DIRECTION('NONE', (0, 0, -1));
#1051 = VECTOR('NONE', #1050, 1); #1051 = VECTOR('NONE', #1050, 1);
#1052 = CARTESIAN_POINT('NONE', (-0.0072238724853847325, -0.03528499188783953, 0.020637499999999996)); #1052 = CARTESIAN_POINT('NONE', (-0.007223872485384732, -0.03528499188783953, 0.020637499999999996));
#1053 = LINE('NONE', #1052, #1051); #1053 = LINE('NONE', #1052, #1051);
#1054 = DIRECTION('NONE', (0.07473009358642402, -0.9972037971811802, 0)); #1054 = DIRECTION('NONE', (0.07473009358642413, -0.9972037971811802, 0));
#1055 = VECTOR('NONE', #1054, 1); #1055 = VECTOR('NONE', #1054, 1);
#1056 = CARTESIAN_POINT('NONE', (-0.007881579512048209, -0.026508501548468247, 0.014287499999999998)); #1056 = CARTESIAN_POINT('NONE', (-0.007881579512048209, -0.026508501548468247, 0.014287499999999998));
#1057 = LINE('NONE', #1056, #1055); #1057 = LINE('NONE', #1056, #1055);
#1058 = DIRECTION('NONE', (-0.826238774315995, -0.5633200580636218, 0)); #1058 = DIRECTION('NONE', (-0.826238774315995, -0.563320058063622, 0));
#1059 = VECTOR('NONE', #1058, 1); #1059 = VECTOR('NONE', #1058, 1);
#1060 = CARTESIAN_POINT('NONE', (-0.0072238724853847325, -0.03528499188783953, 0.020637499999999996)); #1060 = CARTESIAN_POINT('NONE', (-0.007223872485384732, -0.03528499188783953, 0.020637499999999996));
#1061 = LINE('NONE', #1060, #1059); #1061 = LINE('NONE', #1060, #1059);
#1062 = DIRECTION('NONE', (0, 0, -1)); #1062 = DIRECTION('NONE', (0, 0, -1));
#1063 = VECTOR('NONE', #1062, 1); #1063 = VECTOR('NONE', #1062, 1);
#1064 = CARTESIAN_POINT('NONE', (-0.014495682562017234, -0.04024282805086327, 0.020637499999999996)); #1064 = CARTESIAN_POINT('NONE', (-0.014495682562017232, -0.04024282805086327, 0.020637499999999996));
#1065 = LINE('NONE', #1064, #1063); #1065 = LINE('NONE', #1064, #1063);
#1066 = DIRECTION('NONE', (-0.826238774315995, -0.5633200580636218, 0)); #1066 = DIRECTION('NONE', (-0.826238774315995, -0.563320058063622, 0));
#1067 = VECTOR('NONE', #1066, 1); #1067 = VECTOR('NONE', #1066, 1);
#1068 = CARTESIAN_POINT('NONE', (-0.0072238724853847325, -0.03528499188783953, 0.014287499999999998)); #1068 = CARTESIAN_POINT('NONE', (-0.007223872485384732, -0.03528499188783953, 0.014287499999999998));
#1069 = LINE('NONE', #1068, #1067); #1069 = LINE('NONE', #1068, #1067);
#1070 = DIRECTION('NONE', (-0.9009688679024193, 0.4338837391175578, 0)); #1070 = DIRECTION('NONE', (-0.9009688679024194, 0.4338837391175579, 0));
#1071 = VECTOR('NONE', #1070, 1); #1071 = VECTOR('NONE', #1070, 1);
#1072 = CARTESIAN_POINT('NONE', (-0.014495682562017234, -0.04024282805086327, 0.020637499999999996)); #1072 = CARTESIAN_POINT('NONE', (-0.014495682562017232, -0.04024282805086327, 0.020637499999999996));
#1073 = LINE('NONE', #1072, #1071); #1073 = LINE('NONE', #1072, #1071);
#1074 = DIRECTION('NONE', (0, 0, -1)); #1074 = DIRECTION('NONE', (0, 0, -1));
#1075 = VECTOR('NONE', #1074, 1); #1075 = VECTOR('NONE', #1074, 1);
#1076 = CARTESIAN_POINT('NONE', (-0.022425199665313218, -0.036424173874515735, 0.020637499999999996)); #1076 = CARTESIAN_POINT('NONE', (-0.022425199665313214, -0.036424173874515735, 0.020637499999999996));
#1077 = LINE('NONE', #1076, #1075); #1077 = LINE('NONE', #1076, #1075);
#1078 = DIRECTION('NONE', (-0.9009688679024193, 0.4338837391175578, 0)); #1078 = DIRECTION('NONE', (-0.9009688679024194, 0.4338837391175579, 0));
#1079 = VECTOR('NONE', #1078, 1); #1079 = VECTOR('NONE', #1078, 1);
#1080 = CARTESIAN_POINT('NONE', (-0.014495682562017234, -0.04024282805086327, 0.014287499999999998)); #1080 = CARTESIAN_POINT('NONE', (-0.014495682562017232, -0.04024282805086327, 0.014287499999999998));
#1081 = LINE('NONE', #1080, #1079); #1081 = LINE('NONE', #1080, #1079);
#1082 = DIRECTION('NONE', (-0.07473009358642409, 0.9972037971811802, 0)); #1082 = DIRECTION('NONE', (-0.07473009358642449, 0.9972037971811801, 0));
#1083 = VECTOR('NONE', #1082, 1); #1083 = VECTOR('NONE', #1082, 1);
#1084 = CARTESIAN_POINT('NONE', (-0.022425199665313218, -0.036424173874515735, 0.020637499999999996)); #1084 = CARTESIAN_POINT('NONE', (-0.022425199665313214, -0.036424173874515735, 0.020637499999999996));
#1085 = LINE('NONE', #1084, #1083); #1085 = LINE('NONE', #1084, #1083);
#1086 = DIRECTION('NONE', (-0.07473009358642409, 0.9972037971811802, 0)); #1086 = DIRECTION('NONE', (-0.07473009358642449, 0.9972037971811801, 0));
#1087 = VECTOR('NONE', #1086, 1); #1087 = VECTOR('NONE', #1086, 1);
#1088 = CARTESIAN_POINT('NONE', (-0.022425199665313218, -0.036424173874515735, 0.014287499999999998)); #1088 = CARTESIAN_POINT('NONE', (-0.022425199665313214, -0.036424173874515735, 0.014287499999999998));
#1089 = LINE('NONE', #1088, #1087); #1089 = LINE('NONE', #1088, #1087);
#1090 = DIRECTION('NONE', (-0.43388373911755884, -0.9009688679024187, -0)); #1090 = DIRECTION('NONE', (-0.43388373911755884, -0.9009688679024187, -0));
#1091 = DIRECTION('NONE', (0, -0, 0.9999999999999999)); #1091 = DIRECTION('NONE', (0, -0, 0.9999999999999999));
@ -1157,37 +1157,37 @@ DATA;
#1141 = VECTOR('NONE', #1140, 1); #1141 = VECTOR('NONE', #1140, 1);
#1142 = CARTESIAN_POINT('NONE', (-0.02563926551194579, -0.010365713383932076, 0.0142875)); #1142 = CARTESIAN_POINT('NONE', (-0.02563926551194579, -0.010365713383932076, 0.0142875));
#1143 = LINE('NONE', #1142, #1141); #1143 = LINE('NONE', #1142, #1141);
#1144 = DIRECTION('NONE', (-0.9555728057861403, 0.29475517441090565, 0)); #1144 = DIRECTION('NONE', (-0.9555728057861405, 0.29475517441090526, 0));
#1145 = VECTOR('NONE', #1144, 1); #1145 = VECTOR('NONE', #1144, 1);
#1146 = CARTESIAN_POINT('NONE', (-0.032090928341107286, -0.01635198166632771, 0.0206375)); #1146 = CARTESIAN_POINT('NONE', (-0.032090928341107286, -0.01635198166632771, 0.0206375));
#1147 = LINE('NONE', #1146, #1145); #1147 = LINE('NONE', #1146, #1145);
#1148 = DIRECTION('NONE', (0, 0, -1)); #1148 = DIRECTION('NONE', (0, 0, -1));
#1149 = VECTOR('NONE', #1148, 1); #1149 = VECTOR('NONE', #1148, 1);
#1150 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.01375781190081989, 0.0206375)); #1150 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.013757811900819893, 0.0206375));
#1151 = LINE('NONE', #1150, #1149); #1151 = LINE('NONE', #1150, #1149);
#1152 = DIRECTION('NONE', (-0.9555728057861403, 0.29475517441090565, 0)); #1152 = DIRECTION('NONE', (-0.9555728057861405, 0.29475517441090526, 0));
#1153 = VECTOR('NONE', #1152, 1); #1153 = VECTOR('NONE', #1152, 1);
#1154 = CARTESIAN_POINT('NONE', (-0.032090928341107286, -0.01635198166632771, 0.0142875)); #1154 = CARTESIAN_POINT('NONE', (-0.032090928341107286, -0.01635198166632771, 0.0142875));
#1155 = LINE('NONE', #1154, #1153); #1155 = LINE('NONE', #1154, #1153);
#1156 = DIRECTION('NONE', (-0.22252093395631356, 0.9749279121818238, 0)); #1156 = DIRECTION('NONE', (-0.22252093395631356, 0.9749279121818238, 0));
#1157 = VECTOR('NONE', #1156, 1); #1157 = VECTOR('NONE', #1156, 1);
#1158 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.01375781190081989, 0.0206375)); #1158 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.013757811900819893, 0.0206375));
#1159 = LINE('NONE', #1158, #1157); #1159 = LINE('NONE', #1158, #1157);
#1160 = DIRECTION('NONE', (0, 0, -1)); #1160 = DIRECTION('NONE', (0, 0, -1));
#1161 = VECTOR('NONE', #1160, 1); #1161 = VECTOR('NONE', #1160, 1);
#1162 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.005177373852916439, 0.0206375)); #1162 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.0051773738529164405, 0.0206375));
#1163 = LINE('NONE', #1162, #1161); #1163 = LINE('NONE', #1162, #1161);
#1164 = DIRECTION('NONE', (-0.22252093395631356, 0.9749279121818238, 0)); #1164 = DIRECTION('NONE', (-0.22252093395631356, 0.9749279121818238, 0));
#1165 = VECTOR('NONE', #1164, 1); #1165 = VECTOR('NONE', #1164, 1);
#1166 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.01375781190081989, 0.0142875)); #1166 = CARTESIAN_POINT('NONE', (-0.040501020162111684, -0.013757811900819893, 0.0142875));
#1167 = LINE('NONE', #1166, #1165); #1167 = LINE('NONE', #1166, #1165);
#1168 = DIRECTION('NONE', (0.7330518718298267, 0.680172737770919, 0)); #1168 = DIRECTION('NONE', (0.7330518718298267, 0.6801727377709191, 0));
#1169 = VECTOR('NONE', #1168, 1); #1169 = VECTOR('NONE', #1168, 1);
#1170 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.005177373852916439, 0.0206375)); #1170 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.0051773738529164405, 0.0206375));
#1171 = LINE('NONE', #1170, #1169); #1171 = LINE('NONE', #1170, #1169);
#1172 = DIRECTION('NONE', (0.7330518718298267, 0.680172737770919, 0)); #1172 = DIRECTION('NONE', (0.7330518718298267, 0.6801727377709191, 0));
#1173 = VECTOR('NONE', #1172, 1); #1173 = VECTOR('NONE', #1172, 1);
#1174 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.005177373852916439, 0.0142875)); #1174 = CARTESIAN_POINT('NONE', (-0.042459449153954595, -0.0051773738529164405, 0.0142875));
#1175 = LINE('NONE', #1174, #1173); #1175 = LINE('NONE', #1174, #1173);
#1176 = DIRECTION('NONE', (-0.9749279121818236, -0.22252093395631475, -0)); #1176 = DIRECTION('NONE', (-0.9749279121818236, -0.22252093395631475, -0));
#1177 = DIRECTION('NONE', (0, -0, 1.0000000000000002)); #1177 = DIRECTION('NONE', (0, -0, 1.0000000000000002));
@ -1243,29 +1243,29 @@ DATA;
#1227 = VECTOR('NONE', #1226, 1); #1227 = VECTOR('NONE', #1226, 1);
#1228 = CARTESIAN_POINT('NONE', (-0.02409006163564487, 0.013582668380723755, 0.0142875)); #1228 = CARTESIAN_POINT('NONE', (-0.02409006163564487, 0.013582668380723755, 0.0142875));
#1229 = LINE('NONE', #1228, #1227); #1229 = LINE('NONE', #1228, #1227);
#1230 = DIRECTION('NONE', (-0.36534102436639493, 0.9308737486442044, 0)); #1230 = DIRECTION('NONE', (-0.3653410243663956, 0.930873748644204, 0));
#1231 = VECTOR('NONE', #1230, 1); #1231 = VECTOR('NONE', #1230, 1);
#1232 = CARTESIAN_POINT('NONE', (-0.032792860620334846, 0.014894404269566884, 0.0206375)); #1232 = CARTESIAN_POINT('NONE', (-0.032792860620334846, 0.014894404269566884, 0.0206375));
#1233 = LINE('NONE', #1232, #1231); #1233 = LINE('NONE', #1232, #1231);
#1234 = DIRECTION('NONE', (0, 0, -1)); #1234 = DIRECTION('NONE', (0, 0, -1));
#1235 = VECTOR('NONE', #1234, 1); #1235 = VECTOR('NONE', #1234, 1);
#1236 = CARTESIAN_POINT('NONE', (-0.036008263509885924, 0.02308711721875939, 0.0206375)); #1236 = CARTESIAN_POINT('NONE', (-0.03600826350988593, 0.02308711721875939, 0.0206375));
#1237 = LINE('NONE', #1236, #1235); #1237 = LINE('NONE', #1236, #1235);
#1238 = DIRECTION('NONE', (-0.36534102436639493, 0.9308737486442044, 0)); #1238 = DIRECTION('NONE', (-0.3653410243663956, 0.930873748644204, 0));
#1239 = VECTOR('NONE', #1238, 1); #1239 = VECTOR('NONE', #1238, 1);
#1240 = CARTESIAN_POINT('NONE', (-0.032792860620334846, 0.014894404269566884, 0.0142875)); #1240 = CARTESIAN_POINT('NONE', (-0.032792860620334846, 0.014894404269566884, 0.0142875));
#1241 = LINE('NONE', #1240, #1239); #1241 = LINE('NONE', #1240, #1239);
#1242 = DIRECTION('NONE', (0.6234898018587333, 0.7818314824680301, 0)); #1242 = DIRECTION('NONE', (0.6234898018587336, 0.7818314824680297, 0));
#1243 = VECTOR('NONE', #1242, 1); #1243 = VECTOR('NONE', #1242, 1);
#1244 = CARTESIAN_POINT('NONE', (-0.036008263509885924, 0.02308711721875939, 0.0206375)); #1244 = CARTESIAN_POINT('NONE', (-0.03600826350988593, 0.02308711721875939, 0.0206375));
#1245 = LINE('NONE', #1244, #1243); #1245 = LINE('NONE', #1244, #1243);
#1246 = DIRECTION('NONE', (0, 0, -1)); #1246 = DIRECTION('NONE', (0, 0, -1));
#1247 = VECTOR('NONE', #1246, 1); #1247 = VECTOR('NONE', #1246, 1);
#1248 = CARTESIAN_POINT('NONE', (-0.03052086741474703, 0.029968094279108768, 0.0206375)); #1248 = CARTESIAN_POINT('NONE', (-0.03052086741474703, 0.029968094279108768, 0.0206375));
#1249 = LINE('NONE', #1248, #1247); #1249 = LINE('NONE', #1248, #1247);
#1250 = DIRECTION('NONE', (0.6234898018587333, 0.7818314824680301, 0)); #1250 = DIRECTION('NONE', (0.6234898018587336, 0.7818314824680297, 0));
#1251 = VECTOR('NONE', #1250, 1); #1251 = VECTOR('NONE', #1250, 1);
#1252 = CARTESIAN_POINT('NONE', (-0.036008263509885924, 0.02308711721875939, 0.0142875)); #1252 = CARTESIAN_POINT('NONE', (-0.03600826350988593, 0.02308711721875939, 0.0142875));
#1253 = LINE('NONE', #1252, #1251); #1253 = LINE('NONE', #1252, #1251);
#1254 = DIRECTION('NONE', (0.9888308262251286, -0.14904226617617397, 0)); #1254 = DIRECTION('NONE', (0.9888308262251286, -0.14904226617617397, 0));
#1255 = VECTOR('NONE', #1254, 1); #1255 = VECTOR('NONE', #1254, 1);
@ -3385,20 +3385,20 @@ DATA;
#3369 = DIRECTION('NONE', (-0, -1, 0)); #3369 = DIRECTION('NONE', (-0, -1, 0));
#3370 = AXIS2_PLACEMENT_3D('NONE', #3368, #3369, $); #3370 = AXIS2_PLACEMENT_3D('NONE', #3368, #3369, $);
#3371 = PLANE('NONE', #3370); #3371 = PLANE('NONE', #3370);
#3372 = CARTESIAN_POINT('NONE', (-0.006600825000000002, 0.031114011909376384, 0.0174625)); #3372 = CARTESIAN_POINT('NONE', (-0.006600825000000004, 0.031114011909376387, 0.0174625));
#3373 = DIRECTION('NONE', (-0.866025403784439, -0.49999999999999956, -0.000000000000000000000000000000006162975822039155)); #3373 = DIRECTION('NONE', (-0.8660254037844388, -0.49999999999999967, -0));
#3374 = AXIS2_PLACEMENT_3D('NONE', #3372, #3373, $); #3374 = AXIS2_PLACEMENT_3D('NONE', #3372, #3373, $);
#3375 = PLANE('NONE', #3374); #3375 = PLANE('NONE', #3374);
#3376 = CARTESIAN_POINT('NONE', (-0.006600824999999999, 0.038735988090623605, 0.0174625)); #3376 = CARTESIAN_POINT('NONE', (-0.006600825000000002, 0.038735988090623605, 0.017462499999999995));
#3377 = DIRECTION('NONE', (-0.8660254037844386, 0.5000000000000001, 0)); #3377 = DIRECTION('NONE', (-0.8660254037844386, 0.5000000000000001, -0));
#3378 = AXIS2_PLACEMENT_3D('NONE', #3376, #3377, $); #3378 = AXIS2_PLACEMENT_3D('NONE', #3376, #3377, $);
#3379 = PLANE('NONE', #3378); #3379 = PLANE('NONE', #3378);
#3380 = CARTESIAN_POINT('NONE', (0.000000000000000003996802888650563, 0.04254697618124722, 0.017462499999999995)); #3380 = CARTESIAN_POINT('NONE', (-0.0000000000000000008881784197001253, 0.04254697618124722, 0.0174625));
#3381 = DIRECTION('NONE', (0, 1, 0)); #3381 = DIRECTION('NONE', (0, 1, 0));
#3382 = AXIS2_PLACEMENT_3D('NONE', #3380, #3381, $); #3382 = AXIS2_PLACEMENT_3D('NONE', #3380, #3381, $);
#3383 = PLANE('NONE', #3382); #3383 = PLANE('NONE', #3382);
#3384 = CARTESIAN_POINT('NONE', (0.006600825, 0.0387359880906236, 0.0174625)); #3384 = CARTESIAN_POINT('NONE', (0.0066008249999999985, 0.0387359880906236, 0.0174625));
#3385 = DIRECTION('NONE', (0.8660254037844388, 0.4999999999999995, 0)); #3385 = DIRECTION('NONE', (0.8660254037844388, 0.49999999999999967, 0));
#3386 = AXIS2_PLACEMENT_3D('NONE', #3384, #3385, $); #3386 = AXIS2_PLACEMENT_3D('NONE', #3384, #3385, $);
#3387 = PLANE('NONE', #3386); #3387 = PLANE('NONE', #3386);
#3388 = CARTESIAN_POINT('NONE', (0, 0.034925, 0.0174625)); #3388 = CARTESIAN_POINT('NONE', (0, 0.034925, 0.0174625));
@ -3422,20 +3422,20 @@ DATA;
#3406 = DIRECTION('NONE', (-0.7818314824680298, -0.6234898018587336, 0)); #3406 = DIRECTION('NONE', (-0.7818314824680298, -0.6234898018587336, 0));
#3407 = AXIS2_PLACEMENT_3D('NONE', #3405, #3406, $); #3407 = AXIS2_PLACEMENT_3D('NONE', #3405, #3406, $);
#3408 = PLANE('NONE', #3407); #3408 = PLANE('NONE', #3407);
#3409 = CARTESIAN_POINT('NONE', (0.020210366985281496, 0.024560001915669396, 0.0174625)); #3409 = CARTESIAN_POINT('NONE', (0.020210366985281496, 0.0245600019156694, 0.0174625));
#3410 = DIRECTION('NONE', (-0.9308737486442041, 0.36534102436639554, -0.000000000000000000000000000000006162975822039155)); #3410 = DIRECTION('NONE', (-0.9308737486442044, 0.36534102436639515, -0));
#3411 = AXIS2_PLACEMENT_3D('NONE', #3409, #3410, $); #3411 = AXIS2_PLACEMENT_3D('NONE', #3409, #3410, $);
#3412 = PLANE('NONE', #3411); #3412 = PLANE('NONE', #3411);
#3413 = CARTESIAN_POINT('NONE', (0.026169467922402028, 0.02931222633468721, 0.0174625)); #3413 = CARTESIAN_POINT('NONE', (0.026169467922402025, 0.02931222633468721, 0.017462499999999995));
#3414 = DIRECTION('NONE', (-0.1490422661761737, 0.9888308262251286, 0)); #3414 = DIRECTION('NONE', (-0.14904226617617383, 0.9888308262251286, -0));
#3415 = AXIS2_PLACEMENT_3D('NONE', #3413, #3414, $); #3415 = AXIS2_PLACEMENT_3D('NONE', #3413, #3414, $);
#3416 = PLANE('NONE', #3415); #3416 = PLANE('NONE', #3415);
#3417 = CARTESIAN_POINT('NONE', (0.03326456546231646, 0.026527605748934077, 0.017462499999999995)); #3417 = CARTESIAN_POINT('NONE', (0.03326456546231646, 0.026527605748934088, 0.0174625));
#3418 = DIRECTION('NONE', (0.7818314824680298, 0.6234898018587336, 0)); #3418 = DIRECTION('NONE', (0.7818314824680295, 0.6234898018587338, 0));
#3419 = AXIS2_PLACEMENT_3D('NONE', #3417, #3418, $); #3419 = AXIS2_PLACEMENT_3D('NONE', #3417, #3418, $);
#3420 = PLANE('NONE', #3419); #3420 = PLANE('NONE', #3419);
#3421 = CARTESIAN_POINT('NONE', (0.03440056206511037, 0.018990760744163143, 0.0174625)); #3421 = CARTESIAN_POINT('NONE', (0.03440056206511037, 0.018990760744163143, 0.0174625));
#3422 = DIRECTION('NONE', (0.9308737486442041, -0.3653410243663953, 0)); #3422 = DIRECTION('NONE', (0.9308737486442044, -0.36534102436639526, 0));
#3423 = AXIS2_PLACEMENT_3D('NONE', #3421, #3422, $); #3423 = AXIS2_PLACEMENT_3D('NONE', #3421, #3422, $);
#3424 = PLANE('NONE', #3423); #3424 = PLANE('NONE', #3423);
#3425 = CARTESIAN_POINT('NONE', (0.02730546452519594, 0.02177538132991627, 0.0174625)); #3425 = CARTESIAN_POINT('NONE', (0.02730546452519594, 0.02177538132991627, 0.0174625));
@ -3459,16 +3459,16 @@ DATA;
#3443 = DIRECTION('NONE', (-0.9749279121818235, 0.22252093395631442, -0.0000000000000000000000000000000030814879110195774)); #3443 = DIRECTION('NONE', (-0.9749279121818235, 0.22252093395631442, -0.0000000000000000000000000000000030814879110195774));
#3444 = AXIS2_PLACEMENT_3D('NONE', #3442, #3443, $); #3444 = AXIS2_PLACEMENT_3D('NONE', #3442, #3443, $);
#3445 = PLANE('NONE', #3444); #3445 = PLANE('NONE', #3444);
#3446 = CARTESIAN_POINT('NONE', (0.0318027404142909, -0.00048819045327473263, 0.0174625)); #3446 = CARTESIAN_POINT('NONE', (0.03180274041429091, -0.0004881904532747314, 0.0174625));
#3447 = DIRECTION('NONE', (-0.2947551744109037, 0.955572805786141, -0.000000000000000000000000000000006162975822039155)); #3447 = DIRECTION('NONE', (-0.2947551744109037, 0.9555728057861408, -0));
#3448 = AXIS2_PLACEMENT_3D('NONE', #3446, #3447, $); #3448 = AXIS2_PLACEMENT_3D('NONE', #3446, #3447, $);
#3449 = PLANE('NONE', #3448); #3449 = PLANE('NONE', #3448);
#3450 = CARTESIAN_POINT('NONE', (0.039233617739373845, -0.0021842397117186486, 0.0174625)); #3450 = CARTESIAN_POINT('NONE', (0.039233617739373845, -0.002184239711718646, 0.017462499999999995));
#3451 = DIRECTION('NONE', (0.6801727377709197, 0.733051871829826, 0)); #3451 = DIRECTION('NONE', (0.6801727377709199, 0.7330518718298258, 0));
#3452 = AXIS2_PLACEMENT_3D('NONE', #3450, #3451, $); #3452 = AXIS2_PLACEMENT_3D('NONE', #3450, #3451, $);
#3453 = PLANE('NONE', #3452); #3453 = PLANE('NONE', #3452);
#3454 = CARTESIAN_POINT('NONE', (0.041480234658033126, -0.009467592876868191, 0.017462499999999995)); #3454 = CARTESIAN_POINT('NONE', (0.041480234658033126, -0.00946759287686819, 0.0174625));
#3455 = DIRECTION('NONE', (0.9749279121818237, -0.22252093395631437, 0)); #3455 = DIRECTION('NONE', (0.9749279121818235, -0.22252093395631442, 0));
#3456 = AXIS2_PLACEMENT_3D('NONE', #3454, #3455, $); #3456 = AXIS2_PLACEMENT_3D('NONE', #3454, #3455, $);
#3457 = PLANE('NONE', #3456); #3457 = PLANE('NONE', #3456);
#3458 = CARTESIAN_POINT('NONE', (0.036295974251609464, -0.015054896783573819, 0.0174625)); #3458 = CARTESIAN_POINT('NONE', (0.036295974251609464, -0.015054896783573819, 0.0174625));
@ -3496,20 +3496,20 @@ DATA;
#3480 = DIRECTION('NONE', (-0.43388373911755784, 0.9009688679024194, 0.000000000000000000000000000000006162975822039155)); #3480 = DIRECTION('NONE', (-0.43388373911755784, 0.9009688679024194, 0.000000000000000000000000000000006162975822039155));
#3481 = AXIS2_PLACEMENT_3D('NONE', #3479, #3480, $); #3481 = AXIS2_PLACEMENT_3D('NONE', #3479, #3480, $);
#3482 = PLANE('NONE', #3481); #3482 = PLANE('NONE', #3481);
#3483 = CARTESIAN_POINT('NONE', (0.01944700165366045, -0.025168765453632568, 0.0174625)); #3483 = CARTESIAN_POINT('NONE', (0.01944700165366045, -0.02516876545363257, 0.0174625));
#3484 = DIRECTION('NONE', (0.5633200580636224, 0.8262387743159947, 0)); #3484 = DIRECTION('NONE', (0.5633200580636222, 0.8262387743159947, 0));
#3485 = AXIS2_PLACEMENT_3D('NONE', #3483, #3484, $); #3485 = AXIS2_PLACEMENT_3D('NONE', #3483, #3484, $);
#3486 = PLANE('NONE', #3485); #3486 = PLANE('NONE', #3485);
#3487 = CARTESIAN_POINT('NONE', (0.02275405317864496, -0.03203592870483008, 0.0174625)); #3487 = CARTESIAN_POINT('NONE', (0.022754053178644963, -0.032035928704830074, 0.017462499999999995));
#3488 = DIRECTION('NONE', (0.9972037971811802, -0.07473009358642456, 0.0000000000000000000000000000000015407439555097887)); #3488 = DIRECTION('NONE', (0.9972037971811802, -0.07473009358642414, 0));
#3489 = AXIS2_PLACEMENT_3D('NONE', #3487, #3488, $); #3489 = AXIS2_PLACEMENT_3D('NONE', #3487, #3488, $);
#3490 = PLANE('NONE', #3489); #3490 = PLANE('NONE', #3489);
#3491 = CARTESIAN_POINT('NONE', (0.018460441113665228, -0.03833350096268949, 0.017462499999999995)); #3491 = CARTESIAN_POINT('NONE', (0.018460441113665235, -0.03833350096268949, 0.0174625));
#3492 = DIRECTION('NONE', (0.43388373911755723, -0.9009688679024195, 0)); #3492 = DIRECTION('NONE', (0.4338837391175577, -0.9009688679024195, 0));
#3493 = AXIS2_PLACEMENT_3D('NONE', #3491, #3492, $); #3493 = AXIS2_PLACEMENT_3D('NONE', #3491, #3492, $);
#3494 = PLANE('NONE', #3493); #3494 = PLANE('NONE', #3493);
#3495 = CARTESIAN_POINT('NONE', (0.01085977752370099, -0.03776390996935139, 0.0174625)); #3495 = CARTESIAN_POINT('NONE', (0.01085977752370099, -0.03776390996935139, 0.0174625));
#3496 = DIRECTION('NONE', (-0.5633200580636213, -0.8262387743159954, -0)); #3496 = DIRECTION('NONE', (-0.5633200580636212, -0.8262387743159955, -0));
#3497 = AXIS2_PLACEMENT_3D('NONE', #3495, #3496, $); #3497 = AXIS2_PLACEMENT_3D('NONE', #3495, #3496, $);
#3498 = PLANE('NONE', #3497); #3498 = PLANE('NONE', #3497);
#3499 = CARTESIAN_POINT('NONE', (0.015153389588680722, -0.03146633771149198, 0.0174625)); #3499 = CARTESIAN_POINT('NONE', (0.015153389588680722, -0.03146633771149198, 0.0174625));
@ -3533,20 +3533,20 @@ DATA;
#3517 = DIRECTION('NONE', (0.43388373911755823, 0.9009688679024191, 0)); #3517 = DIRECTION('NONE', (0.43388373911755823, 0.9009688679024191, 0));
#3518 = AXIS2_PLACEMENT_3D('NONE', #3516, #3517, $); #3518 = AXIS2_PLACEMENT_3D('NONE', #3516, #3517, $);
#3519 = PLANE('NONE', #3518); #3519 = PLANE('NONE', #3518);
#3520 = CARTESIAN_POINT('NONE', (-0.00755272599871647, -0.030896746718153883, 0.017462499999999995)); #3520 = CARTESIAN_POINT('NONE', (-0.007552725998716469, -0.030896746718153886, 0.017462499999999992));
#3521 = DIRECTION('NONE', (0.9972037971811802, 0.074730093586424, 0)); #3521 = DIRECTION('NONE', (0.9972037971811802, 0.07473009358642403, 0));
#3522 = AXIS2_PLACEMENT_3D('NONE', #3520, #3521, $); #3522 = AXIS2_PLACEMENT_3D('NONE', #3520, #3521, $);
#3523 = PLANE('NONE', #3522); #3523 = PLANE('NONE', #3522);
#3524 = CARTESIAN_POINT('NONE', (-0.010859777523700983, -0.037763909969351396, 0.017462499999999995)); #3524 = CARTESIAN_POINT('NONE', (-0.01085977752370098, -0.037763909969351396, 0.017462499999999995));
#3525 = DIRECTION('NONE', (0.5633200580636216, -0.8262387743159952, 0)); #3525 = DIRECTION('NONE', (0.563320058063622, -0.8262387743159951, 0));
#3526 = AXIS2_PLACEMENT_3D('NONE', #3524, #3525, $); #3526 = AXIS2_PLACEMENT_3D('NONE', #3524, #3525, $);
#3527 = PLANE('NONE', #3526); #3527 = PLANE('NONE', #3526);
#3528 = CARTESIAN_POINT('NONE', (-0.018460441113665228, -0.03833350096268949, 0.017462499999999992)); #3528 = CARTESIAN_POINT('NONE', (-0.018460441113665224, -0.0383335009626895, 0.017462499999999995));
#3529 = DIRECTION('NONE', (-0.4338837391175573, -0.9009688679024197, -0)); #3529 = DIRECTION('NONE', (-0.43388373911755745, -0.9009688679024194, 0.000000000000000000000000000000006162975822039155));
#3530 = AXIS2_PLACEMENT_3D('NONE', #3528, #3529, $); #3530 = AXIS2_PLACEMENT_3D('NONE', #3528, #3529, $);
#3531 = PLANE('NONE', #3530); #3531 = PLANE('NONE', #3530);
#3532 = CARTESIAN_POINT('NONE', (-0.022754053178644953, -0.03203592870483008, 0.017462499999999995)); #3532 = CARTESIAN_POINT('NONE', (-0.022754053178644953, -0.03203592870483008, 0.017462499999999995));
#3533 = DIRECTION('NONE', (-0.9972037971811802, -0.07473009358642368, 0)); #3533 = DIRECTION('NONE', (-0.9972037971811802, -0.07473009358642413, -0.0000000000000000000000000000000015407439555097887));
#3534 = AXIS2_PLACEMENT_3D('NONE', #3532, #3533, $); #3534 = AXIS2_PLACEMENT_3D('NONE', #3532, #3533, $);
#3535 = PLANE('NONE', #3534); #3535 = PLANE('NONE', #3534);
#3536 = CARTESIAN_POINT('NONE', (-0.015153389588680708, -0.031466337711491994, 0.017462499999999995)); #3536 = CARTESIAN_POINT('NONE', (-0.015153389588680708, -0.031466337711491994, 0.017462499999999995));
@ -3570,20 +3570,20 @@ DATA;
#3554 = DIRECTION('NONE', (0.9749279121818237, 0.22252093395631356, 0)); #3554 = DIRECTION('NONE', (0.9749279121818237, 0.22252093395631356, 0));
#3555 = AXIS2_PLACEMENT_3D('NONE', #3553, #3554, $); #3555 = AXIS2_PLACEMENT_3D('NONE', #3553, #3554, $);
#3556 = PLANE('NONE', #3555); #3556 = PLANE('NONE', #3555);
#3557 = CARTESIAN_POINT('NONE', (-0.028865096926526532, -0.01335884752512989, 0.0174625)); #3557 = CARTESIAN_POINT('NONE', (-0.028865096926526532, -0.013358847525129893, 0.0174625));
#3558 = DIRECTION('NONE', (0.6801727377709186, -0.7330518718298271, 0.000000000000000000000000000000006162975822039155)); #3558 = DIRECTION('NONE', (0.6801727377709185, -0.7330518718298272, 0));
#3559 = AXIS2_PLACEMENT_3D('NONE', #3557, #3558, $); #3559 = AXIS2_PLACEMENT_3D('NONE', #3557, #3558, $);
#3560 = PLANE('NONE', #3559); #3560 = PLANE('NONE', #3559);
#3561 = CARTESIAN_POINT('NONE', (-0.03629597425160948, -0.015054896783573798, 0.0174625)); #3561 = CARTESIAN_POINT('NONE', (-0.03629597425160948, -0.0150548967835738, 0.017462499999999995));
#3562 = DIRECTION('NONE', (-0.2947551744109055, -0.9555728057861405, -0.000000000000000000000000000000006162975822039155)); #3562 = DIRECTION('NONE', (-0.2947551744109054, -0.9555728057861405, -0));
#3563 = AXIS2_PLACEMENT_3D('NONE', #3561, #3562, $); #3563 = AXIS2_PLACEMENT_3D('NONE', #3561, #3562, $);
#3564 = PLANE('NONE', #3563); #3564 = PLANE('NONE', #3563);
#3565 = CARTESIAN_POINT('NONE', (-0.041480234658033126, -0.009467592876868158, 0.017462499999999995)); #3565 = CARTESIAN_POINT('NONE', (-0.04148023465803313, -0.009467592876868165, 0.0174625));
#3566 = DIRECTION('NONE', (-0.974927912181824, -0.22252093395631345, 0)); #3566 = DIRECTION('NONE', (-0.974927912181824, -0.2225209339563127, -0));
#3567 = AXIS2_PLACEMENT_3D('NONE', #3565, #3566, $); #3567 = AXIS2_PLACEMENT_3D('NONE', #3565, #3566, $);
#3568 = PLANE('NONE', #3567); #3568 = PLANE('NONE', #3567);
#3569 = CARTESIAN_POINT('NONE', (-0.03923361773937384, -0.0021842397117186212, 0.0174625)); #3569 = CARTESIAN_POINT('NONE', (-0.03923361773937384, -0.002184239711718622, 0.0174625));
#3570 = DIRECTION('NONE', (-0.6801727377709191, 0.7330518718298266, -0)); #3570 = DIRECTION('NONE', (-0.6801727377709194, 0.7330518718298266, -0));
#3571 = AXIS2_PLACEMENT_3D('NONE', #3569, #3570, $); #3571 = AXIS2_PLACEMENT_3D('NONE', #3569, #3570, $);
#3572 = PLANE('NONE', #3571); #3572 = PLANE('NONE', #3571);
#3573 = CARTESIAN_POINT('NONE', (-0.0340493573329502, -0.007771543618424253, 0.0174625)); #3573 = CARTESIAN_POINT('NONE', (-0.0340493573329502, -0.007771543618424253, 0.0174625));
@ -3608,15 +3608,15 @@ DATA;
#3592 = AXIS2_PLACEMENT_3D('NONE', #3590, #3591, $); #3592 = AXIS2_PLACEMENT_3D('NONE', #3590, #3591, $);
#3593 = PLANE('NONE', #3592); #3593 = PLANE('NONE', #3592);
#3594 = CARTESIAN_POINT('NONE', (-0.028441461127989853, 0.014238536325145318, 0.0174625)); #3594 = CARTESIAN_POINT('NONE', (-0.028441461127989853, 0.014238536325145318, 0.0174625));
#3595 = DIRECTION('NONE', (-0.1490422661761743, -0.9888308262251286, 0.0000000000000000000000000000000030814879110195774)); #3595 = DIRECTION('NONE', (-0.14904226617617458, -0.9888308262251286, -0));
#3596 = AXIS2_PLACEMENT_3D('NONE', #3594, #3595, $); #3596 = AXIS2_PLACEMENT_3D('NONE', #3594, #3595, $);
#3597 = PLANE('NONE', #3596); #3597 = PLANE('NONE', #3596);
#3598 = CARTESIAN_POINT('NONE', (-0.03440056206511038, 0.018990760744163133, 0.0174625)); #3598 = CARTESIAN_POINT('NONE', (-0.03440056206511038, 0.018990760744163133, 0.017462499999999995));
#3599 = DIRECTION('NONE', (-0.9308737486442047, -0.3653410243663939, 0)); #3599 = DIRECTION('NONE', (-0.9308737486442044, -0.36534102436639526, -0));
#3600 = AXIS2_PLACEMENT_3D('NONE', #3598, #3599, $); #3600 = AXIS2_PLACEMENT_3D('NONE', #3598, #3599, $);
#3601 = PLANE('NONE', #3600); #3601 = PLANE('NONE', #3600);
#3602 = CARTESIAN_POINT('NONE', (-0.03326456546231646, 0.026527605748934074, 0.017462499999999995)); #3602 = CARTESIAN_POINT('NONE', (-0.033264565462316466, 0.026527605748934074, 0.0174625));
#3603 = DIRECTION('NONE', (-0.7818314824680305, 0.6234898018587326, -0.00000000000000000000000000000001232595164407831)); #3603 = DIRECTION('NONE', (-0.7818314824680298, 0.6234898018587336, -0));
#3604 = AXIS2_PLACEMENT_3D('NONE', #3602, #3603, $); #3604 = AXIS2_PLACEMENT_3D('NONE', #3602, #3603, $);
#3605 = PLANE('NONE', #3604); #3605 = PLANE('NONE', #3604);
#3606 = CARTESIAN_POINT('NONE', (-0.02616946792240203, 0.029312226334687202, 0.0174625)); #3606 = CARTESIAN_POINT('NONE', (-0.02616946792240203, 0.029312226334687202, 0.0174625));

View File

@ -2571,21 +2571,21 @@ DATA;
#2555 = VERTEX_POINT('NONE', #2554); #2555 = VERTEX_POINT('NONE', #2554);
#2556 = CARTESIAN_POINT('NONE', (-0.007619999999999999, -0.04635499999999999, 0.0254)); #2556 = CARTESIAN_POINT('NONE', (-0.007619999999999999, -0.04635499999999999, 0.0254));
#2557 = VERTEX_POINT('NONE', #2556); #2557 = VERTEX_POINT('NONE', #2556);
#2558 = CARTESIAN_POINT('NONE', (0.0020980400000000002, -0.04167632, 0.0238125)); #2558 = CARTESIAN_POINT('NONE', (0.0020980400000000007, -0.04167632, 0.0238125));
#2559 = VERTEX_POINT('NONE', #2558); #2559 = VERTEX_POINT('NONE', #2558);
#2560 = CARTESIAN_POINT('NONE', (0.0012729244244215345, -0.0425658054737599, 0.0238125)); #2560 = CARTESIAN_POINT('NONE', (0.0012729244244215347, -0.0425658054737599, 0.0238125));
#2561 = VERTEX_POINT('NONE', #2560); #2561 = VERTEX_POINT('NONE', #2560);
#2562 = CARTESIAN_POINT('NONE', (0.0012729244244215345, -0.0425658054737599, 0.0254)); #2562 = CARTESIAN_POINT('NONE', (0.0012729244244215347, -0.0425658054737599, 0.0254));
#2563 = VERTEX_POINT('NONE', #2562); #2563 = VERTEX_POINT('NONE', #2562);
#2564 = CARTESIAN_POINT('NONE', (0.0020980400000000002, -0.04167632, 0.0254)); #2564 = CARTESIAN_POINT('NONE', (0.0020980400000000007, -0.04167632, 0.0254));
#2565 = VERTEX_POINT('NONE', #2564); #2565 = VERTEX_POINT('NONE', #2564);
#2566 = CARTESIAN_POINT('NONE', (-0.0006804866009285434, -0.0446716083246068, 0.0238125)); #2566 = CARTESIAN_POINT('NONE', (-0.000680486600928543, -0.0446716083246068, 0.0238125));
#2567 = VERTEX_POINT('NONE', #2566); #2567 = VERTEX_POINT('NONE', #2566);
#2568 = CARTESIAN_POINT('NONE', (-0.0006804866009285434, -0.0446716083246068, 0.0254)); #2568 = CARTESIAN_POINT('NONE', (-0.000680486600928543, -0.0446716083246068, 0.0254));
#2569 = VERTEX_POINT('NONE', #2568); #2569 = VERTEX_POINT('NONE', #2568);
#2570 = CARTESIAN_POINT('NONE', (-0.0015096417774425879, -0.045565448541457644, 0.0238125)); #2570 = CARTESIAN_POINT('NONE', (-0.0015096417774425877, -0.045565448541457644, 0.0238125));
#2571 = VERTEX_POINT('NONE', #2570); #2571 = VERTEX_POINT('NONE', #2570);
#2572 = CARTESIAN_POINT('NONE', (-0.0015096417774425879, -0.045565448541457644, 0.0254)); #2572 = CARTESIAN_POINT('NONE', (-0.0015096417774425877, -0.045565448541457644, 0.0254));
#2573 = VERTEX_POINT('NONE', #2572); #2573 = VERTEX_POINT('NONE', #2572);
#2574 = CARTESIAN_POINT('NONE', (-0.0010921999999999998, -0.04595367999999999, 0.0238125)); #2574 = CARTESIAN_POINT('NONE', (-0.0010921999999999998, -0.04595367999999999, 0.0238125));
#2575 = VERTEX_POINT('NONE', #2574); #2575 = VERTEX_POINT('NONE', #2574);
@ -10417,58 +10417,58 @@ DATA;
#10401 = VECTOR('NONE', #10400, 1); #10401 = VECTOR('NONE', #10400, 1);
#10402 = CARTESIAN_POINT('NONE', (-0.007619999999999999, -0.04635499999999999, 0.0254)); #10402 = CARTESIAN_POINT('NONE', (-0.007619999999999999, -0.04635499999999999, 0.0254));
#10403 = LINE('NONE', #10402, #10401); #10403 = LINE('NONE', #10402, #10401);
#10404 = DIRECTION('NONE', (-0.6800813455659818, -0.7331366608028573, 0)); #10404 = DIRECTION('NONE', (-0.6800813455659819, -0.7331366608028572, 0));
#10405 = VECTOR('NONE', #10404, 1); #10405 = VECTOR('NONE', #10404, 1);
#10406 = CARTESIAN_POINT('NONE', (0.0020980400000000002, -0.04167632, 0.0238125)); #10406 = CARTESIAN_POINT('NONE', (0.0020980400000000007, -0.04167632, 0.0238125));
#10407 = LINE('NONE', #10406, #10405); #10407 = LINE('NONE', #10406, #10405);
#10408 = DIRECTION('NONE', (0, 0, 1)); #10408 = DIRECTION('NONE', (0, 0, 1));
#10409 = VECTOR('NONE', #10408, 1); #10409 = VECTOR('NONE', #10408, 1);
#10410 = CARTESIAN_POINT('NONE', (0.0012729244244215345, -0.0425658054737599, 0.0238125)); #10410 = CARTESIAN_POINT('NONE', (0.0012729244244215347, -0.0425658054737599, 0.0238125));
#10411 = LINE('NONE', #10410, #10409); #10411 = LINE('NONE', #10410, #10409);
#10412 = DIRECTION('NONE', (-0.6800813455659818, -0.7331366608028573, 0)); #10412 = DIRECTION('NONE', (-0.6800813455659819, -0.7331366608028572, 0));
#10413 = VECTOR('NONE', #10412, 1); #10413 = VECTOR('NONE', #10412, 1);
#10414 = CARTESIAN_POINT('NONE', (0.0020980400000000002, -0.04167632, 0.0254)); #10414 = CARTESIAN_POINT('NONE', (0.0020980400000000007, -0.04167632, 0.0254));
#10415 = LINE('NONE', #10414, #10413); #10415 = LINE('NONE', #10414, #10413);
#10416 = DIRECTION('NONE', (0, 0, 1)); #10416 = DIRECTION('NONE', (0, 0, 1));
#10417 = VECTOR('NONE', #10416, 1); #10417 = VECTOR('NONE', #10416, 1);
#10418 = CARTESIAN_POINT('NONE', (0.0020980400000000002, -0.04167632, 0.0238125)); #10418 = CARTESIAN_POINT('NONE', (0.0020980400000000007, -0.04167632, 0.0238125));
#10419 = LINE('NONE', #10418, #10417); #10419 = LINE('NONE', #10418, #10417);
#10420 = DIRECTION('NONE', (0.5276972660417225, 0.8494325137479091, -0)); #10420 = DIRECTION('NONE', (0.5276972660417221, 0.8494325137479093, -0));
#10421 = DIRECTION('NONE', (0.0000000000000025022368681948567, -0.000000000000001554477292738876, 1)); #10421 = DIRECTION('NONE', (0.0000000000000025022368681948575, -0.0000000000000015544772927388748, 1));
#10422 = CARTESIAN_POINT('NONE', (0.0005008822163118523, -0.04380855921867364, 0.0238125)); #10422 = CARTESIAN_POINT('NONE', (0.0005008822163118532, -0.04380855921867364, 0.0238125));
#10423 = AXIS2_PLACEMENT_3D('NONE', #10422, #10421, #10420); #10423 = AXIS2_PLACEMENT_3D('NONE', #10422, #10421, #10420);
#10424 = CIRCLE('NONE', #10423, 0.001463040000000001); #10424 = CIRCLE('NONE', #10423, 0.0014630400000000007);
#10425 = DIRECTION('NONE', (0, 0, 1)); #10425 = DIRECTION('NONE', (0, 0, 1));
#10426 = VECTOR('NONE', #10425, 1); #10426 = VECTOR('NONE', #10425, 1);
#10427 = CARTESIAN_POINT('NONE', (-0.0006804866009285434, -0.0446716083246068, 0.0238125)); #10427 = CARTESIAN_POINT('NONE', (-0.000680486600928543, -0.0446716083246068, 0.0238125));
#10428 = LINE('NONE', #10427, #10426); #10428 = LINE('NONE', #10427, #10426);
#10429 = DIRECTION('NONE', (0.5276972660417225, 0.8494325137479091, -0)); #10429 = DIRECTION('NONE', (0.5276972660417224, 0.8494325137479088, -0));
#10430 = DIRECTION('NONE', (-0.0000000000000025022368681948567, 0.000000000000001554477292738876, 1)); #10430 = DIRECTION('NONE', (-0.0000000000000025022368681948563, 0.0000000000000015544772927388758, 0.9999999999999998));
#10431 = CARTESIAN_POINT('NONE', (0.0005008822163118523, -0.04380855921867364, 0.0254)); #10431 = CARTESIAN_POINT('NONE', (0.0005008822163118523, -0.04380855921867364, 0.0254));
#10432 = AXIS2_PLACEMENT_3D('NONE', #10431, #10430, #10429); #10432 = AXIS2_PLACEMENT_3D('NONE', #10431, #10430, #10429);
#10433 = CIRCLE('NONE', #10432, 0.001463040000000001); #10433 = CIRCLE('NONE', #10432, 0.0014630400000000014);
#10434 = DIRECTION('NONE', (-0.6800813455659821, -0.733136660802857, 0)); #10434 = DIRECTION('NONE', (-0.6800813455659821, -0.733136660802857, 0));
#10435 = VECTOR('NONE', #10434, 1); #10435 = VECTOR('NONE', #10434, 1);
#10436 = CARTESIAN_POINT('NONE', (-0.000680486600928543, -0.0446716083246068, 0.0238125)); #10436 = CARTESIAN_POINT('NONE', (-0.0006804866009285428, -0.0446716083246068, 0.0238125));
#10437 = LINE('NONE', #10436, #10435); #10437 = LINE('NONE', #10436, #10435);
#10438 = DIRECTION('NONE', (0, 0, 1)); #10438 = DIRECTION('NONE', (0, 0, 1));
#10439 = VECTOR('NONE', #10438, 1); #10439 = VECTOR('NONE', #10438, 1);
#10440 = CARTESIAN_POINT('NONE', (-0.0015096417774425879, -0.045565448541457644, 0.0238125)); #10440 = CARTESIAN_POINT('NONE', (-0.0015096417774425877, -0.045565448541457644, 0.0238125));
#10441 = LINE('NONE', #10440, #10439); #10441 = LINE('NONE', #10440, #10439);
#10442 = DIRECTION('NONE', (-0.6800813455659821, -0.733136660802857, 0)); #10442 = DIRECTION('NONE', (-0.6800813455659821, -0.733136660802857, 0));
#10443 = VECTOR('NONE', #10442, 1); #10443 = VECTOR('NONE', #10442, 1);
#10444 = CARTESIAN_POINT('NONE', (-0.000680486600928543, -0.0446716083246068, 0.0254)); #10444 = CARTESIAN_POINT('NONE', (-0.0006804866009285428, -0.0446716083246068, 0.0254));
#10445 = LINE('NONE', #10444, #10443); #10445 = LINE('NONE', #10444, #10443);
#10446 = DIRECTION('NONE', (-0.7529894373157879, -0.6580326035166139, -0)); #10446 = DIRECTION('NONE', (-0.7529894373157879, -0.6580326035166137, -0));
#10447 = DIRECTION('NONE', (0, 0, -1)); #10447 = DIRECTION('NONE', (0, 0, -1));
#10448 = CARTESIAN_POINT('NONE', (0.0004985810518786167, -0.04381047558787883, 0.0238125)); #10448 = CARTESIAN_POINT('NONE', (0.0004985810518786172, -0.04381047558787883, 0.0238125));
#10449 = AXIS2_PLACEMENT_3D('NONE', #10448, #10447, #10446); #10449 = AXIS2_PLACEMENT_3D('NONE', #10448, #10447, #10446);
#10450 = CIRCLE('NONE', #10449, 0.0026669999999999975); #10450 = CIRCLE('NONE', #10449, 0.002666999999999998);
#10451 = DIRECTION('NONE', (-0.7529894373157879, -0.6580326035166139, -0)); #10451 = DIRECTION('NONE', (-0.7529894373157879, -0.6580326035166137, -0));
#10452 = DIRECTION('NONE', (0, 0, -1)); #10452 = DIRECTION('NONE', (0, 0, -1));
#10453 = CARTESIAN_POINT('NONE', (0.0004985810518786167, -0.04381047558787883, 0.0254)); #10453 = CARTESIAN_POINT('NONE', (0.0004985810518786172, -0.04381047558787883, 0.0254));
#10454 = AXIS2_PLACEMENT_3D('NONE', #10453, #10452, #10451); #10454 = AXIS2_PLACEMENT_3D('NONE', #10453, #10452, #10451);
#10455 = CIRCLE('NONE', #10454, 0.0026669999999999975); #10455 = CIRCLE('NONE', #10454, 0.002666999999999998);
#10456 = DIRECTION('NONE', (0.6800813455659818, 0.7331366608028573, 0)); #10456 = DIRECTION('NONE', (0.6800813455659818, 0.7331366608028573, 0));
#10457 = VECTOR('NONE', #10456, 1); #10457 = VECTOR('NONE', #10456, 1);
#10458 = CARTESIAN_POINT('NONE', (-0.0010921999999999998, -0.04595367999999999, 0.0238125)); #10458 = CARTESIAN_POINT('NONE', (-0.0010921999999999998, -0.04595367999999999, 0.0238125));
@ -15570,24 +15570,24 @@ DATA;
#15554 = DIRECTION('NONE', (1, 0, -0)); #15554 = DIRECTION('NONE', (1, 0, -0));
#15555 = AXIS2_PLACEMENT_3D('NONE', #15553, #15554, $); #15555 = AXIS2_PLACEMENT_3D('NONE', #15553, #15554, $);
#15556 = PLANE('NONE', #15555); #15556 = PLANE('NONE', #15555);
#15557 = CARTESIAN_POINT('NONE', (0.001685482212210767, -0.04212106273687995, 0.024606250000000003)); #15557 = CARTESIAN_POINT('NONE', (0.0016854822122107677, -0.04212106273687995, 0.024606250000000003));
#15558 = DIRECTION('NONE', (-0.7331366608028554, 0.6800813455659833, -0)); #15558 = DIRECTION('NONE', (-0.7331366608028557, 0.6800813455659834, -0));
#15559 = AXIS2_PLACEMENT_3D('NONE', #15557, #15558, $); #15559 = AXIS2_PLACEMENT_3D('NONE', #15557, #15558, $);
#15560 = PLANE('NONE', #15559); #15560 = PLANE('NONE', #15559);
#15561 = CARTESIAN_POINT('NONE', (0.0005008822163118532, -0.04380855921867364, 0.02460625)); #15561 = CARTESIAN_POINT('NONE', (0.0005008822163118532, -0.04380855921867364, 0.02460625));
#15562 = DIRECTION('NONE', (0, 0, 1)); #15562 = DIRECTION('NONE', (0, 0, 1));
#15563 = DIRECTION('NONE', (0.527697266041722, 0.8494325137479093, -0)); #15563 = DIRECTION('NONE', (0.5276972660417221, 0.8494325137479093, -0));
#15564 = AXIS2_PLACEMENT_3D('NONE', #15561, #15562, #15563); #15564 = AXIS2_PLACEMENT_3D('NONE', #15561, #15562, #15563);
#15565 = CYLINDRICAL_SURFACE('NONE', #15564, 0.0014630400000000007); #15565 = CYLINDRICAL_SURFACE('NONE', #15564, 0.0014630400000000007);
#15566 = CARTESIAN_POINT('NONE', (-0.0010950641891855268, -0.045118528433032185, 0.02460625)); #15566 = CARTESIAN_POINT('NONE', (-0.0010950641891855266, -0.045118528433032185, 0.02460625));
#15567 = DIRECTION('NONE', (-0.733136660802859, 0.6800813455659798, -0)); #15567 = DIRECTION('NONE', (-0.733136660802859, 0.6800813455659798, -0));
#15568 = AXIS2_PLACEMENT_3D('NONE', #15566, #15567, $); #15568 = AXIS2_PLACEMENT_3D('NONE', #15566, #15567, $);
#15569 = PLANE('NONE', #15568); #15569 = PLANE('NONE', #15568);
#15570 = CARTESIAN_POINT('NONE', (0.0004985810518786167, -0.04381047558787883, 0.02460625)); #15570 = CARTESIAN_POINT('NONE', (0.0004985810518786172, -0.04381047558787883, 0.02460625));
#15571 = DIRECTION('NONE', (0, 0, -1)); #15571 = DIRECTION('NONE', (0, 0, -1));
#15572 = DIRECTION('NONE', (-0.7529894373157879, -0.6580326035166139, -0)); #15572 = DIRECTION('NONE', (-0.7529894373157879, -0.6580326035166137, -0));
#15573 = AXIS2_PLACEMENT_3D('NONE', #15570, #15571, #15572); #15573 = AXIS2_PLACEMENT_3D('NONE', #15570, #15571, #15572);
#15574 = CYLINDRICAL_SURFACE('NONE', #15573, 0.0026669999999999975); #15574 = CYLINDRICAL_SURFACE('NONE', #15573, 0.002666999999999998);
#15575 = CARTESIAN_POINT('NONE', (-0.0006796422122107669, -0.045508937263120046, 0.024606250000000003)); #15575 = CARTESIAN_POINT('NONE', (-0.0006796422122107669, -0.045508937263120046, 0.024606250000000003));
#15576 = DIRECTION('NONE', (0.7331366608028589, -0.68008134556598, 0)); #15576 = DIRECTION('NONE', (0.7331366608028589, -0.68008134556598, 0));
#15577 = AXIS2_PLACEMENT_3D('NONE', #15575, #15576, $); #15577 = AXIS2_PLACEMENT_3D('NONE', #15575, #15576, $);

View File

@ -3,7 +3,7 @@
# This way we don't start and stop too many engine instances, putting pressure on our cloud. # This way we don't start and stop too many engine instances, putting pressure on our cloud.
uses-engine = { max-threads = 4 } uses-engine = { max-threads = 4 }
# If a test must run after the engine tests, we want to make sure the engine tests are done first. # If a test must run after the engine tests, we want to make sure the engine tests are done first.
after-engine = { depends-on = ["uses-engine"], max-threads = 12 } after-engine = { max-threads = 12 }
[profile.default] [profile.default]
slow-timeout = { period = "30s", terminate-after = 1 } slow-timeout = { period = "30s", terminate-after = 1 }

55
rust/Cargo.lock generated
View File

@ -1783,7 +1783,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.48" version = "0.1.49"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1794,7 +1794,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-derive-docs" name = "kcl-derive-docs"
version = "0.1.48" version = "0.1.49"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"anyhow", "anyhow",
@ -1813,7 +1813,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
version = "0.1.48" version = "0.1.49"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1822,7 +1822,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server" name = "kcl-language-server"
version = "0.2.48" version = "0.2.49"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1843,7 +1843,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.48" version = "0.1.49"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1863,7 +1863,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.2.48" version = "0.2.49"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1931,7 +1931,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-python-bindings" name = "kcl-python-bindings"
version = "0.3.48" version = "0.3.49"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kcl-lib", "kcl-lib",
@ -1946,7 +1946,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.48" version = "0.1.49"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.32", "hyper 0.14.32",
@ -1959,7 +1959,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-to-core" name = "kcl-to-core"
version = "0.1.48" version = "0.1.49"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1973,7 +1973,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-wasm-lib" name = "kcl-wasm-lib"
version = "0.1.48" version = "0.1.49"
dependencies = [ dependencies = [
"bson", "bson",
"console_error_panic_hook", "console_error_panic_hook",
@ -2751,9 +2751,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3" name = "pyo3"
version = "0.22.6" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" checksum = "7f1c6c3591120564d64db2261bec5f910ae454f01def849b9c22835a84695e86"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"indoc", "indoc",
@ -2770,9 +2770,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-build-config" name = "pyo3-build-config"
version = "0.22.6" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" checksum = "e9b6c2b34cf71427ea37c7001aefbaeb85886a074795e35f161f5aecc7620a7a"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"target-lexicon", "target-lexicon",
@ -2780,9 +2780,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-ffi" name = "pyo3-ffi"
version = "0.22.6" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" checksum = "5507651906a46432cdda02cd02dd0319f6064f1374c9147c45b978621d2c3a9c"
dependencies = [ dependencies = [
"libc", "libc",
"pyo3-build-config", "pyo3-build-config",
@ -2790,9 +2790,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-macros" name = "pyo3-macros"
version = "0.22.6" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" checksum = "b0d394b5b4fd8d97d48336bb0dd2aebabad39f1d294edd6bcd2cccf2eefe6f42"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-macros-backend", "pyo3-macros-backend",
@ -2802,9 +2802,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-macros-backend" name = "pyo3-macros-backend"
version = "0.22.6" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" checksum = "fd72da09cfa943b1080f621f024d2ef7e2773df7badd51aa30a2be1f8caa7c8e"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -3097,15 +3097,14 @@ checksum = "e6cd655523701785087f69900df39892fb7b9b0721aa67682f571c38c32ac58a"
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.8" version = "0.17.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
"getrandom 0.2.15", "getrandom 0.2.15",
"libc", "libc",
"spin",
"untrusted", "untrusted",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -3560,12 +3559,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"
@ -3735,9 +3728,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "target-lexicon" name = "target-lexicon"
version = "0.12.16" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
[[package]] [[package]]
name = "tempfile" name = "tempfile"

View File

@ -38,7 +38,7 @@ kittycad = { version = "0.3.28", default-features = false, features = ["js", "re
kittycad-modeling-cmds = { version = "0.2.100", features = ["ts-rs", "websocket"] } kittycad-modeling-cmds = { version = "0.2.100", features = ["ts-rs", "websocket"] }
lazy_static = "1.5.0" lazy_static = "1.5.0"
miette = "7.5.0" miette = "7.5.0"
pyo3 = { version = "0.22.6" } pyo3 = { version = "0.24.0" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = { version = "1" } serde_json = { version = "1" }
slog = "2.7.0" slog = "2.7.0"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.48" version = "0.1.49"
edition = "2021" edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api" repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76" rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-derive-docs" name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros" description = "A tool for generating documentation from Rust derive macros"
version = "0.1.48" version = "0.1.49"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files" description = "A tool for generating tests from a directory of kcl files"
version = "0.1.48" version = "0.1.49"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.48" version = "0.1.49"
edition = "2021" edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false publish = false

View File

@ -72,7 +72,7 @@ impl Build {
} }
fn build_client(sh: &Shell, version: &str, release_tag: &str, target: &Target) -> anyhow::Result<()> { fn build_client(sh: &Shell, version: &str, release_tag: &str, target: &Target) -> anyhow::Result<()> {
let bundle_path = Path::new("server"); let bundle_path = Path::new("kcl-language-server/server");
sh.create_dir(bundle_path)?; sh.create_dir(bundle_path)?;
sh.copy_file(&target.server_path, bundle_path)?; sh.copy_file(&target.server_path, bundle_path)?;
if let Some(symbols_path) = &target.symbols_path { if let Some(symbols_path) = &target.symbols_path {

View File

@ -2,7 +2,7 @@
name = "kcl-language-server" name = "kcl-language-server"
description = "A language server for KCL." description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.48" version = "0.2.49"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -42,13 +42,3 @@ tower-lsp = { version = "0.20.0", default-features = false, features = [
[lints] [lints]
workspace = true workspace = true
[profile.dev]
# Disabling debug info speeds up builds a bunch,
# and we don't rely on it for debugging that much.
debug = 0
[profile.release]
incremental = true
# Set this to 1 or 2 to get more useful backtraces in debugger.
debug = 0

View File

@ -97,7 +97,7 @@ async function getServer(
'You need to manually clone the kcl-lsp repository and ' + 'You need to manually clone the kcl-lsp repository and ' +
'run `cargo install` to build the language server from sources. ' + 'run `cargo install` to build the language server from sources. ' +
'If you feel that your platform should be supported, please create an issue ' + 'If you feel that your platform should be supported, please create an issue ' +
'about that [here](https://github.com/kittycad/kcl-lsp/issues) and we ' + 'about that [here](https://github.com/kittycad/modeling-app/issues) and we ' +
'will consider it.' 'will consider it.'
) )
return undefined return undefined

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language implementation and tools" description = "KittyCAD Language implementation and tools"
version = "0.2.48" version = "0.2.49"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"
@ -145,10 +145,6 @@ name = "lsp_semantic_tokens_benchmark_criterion"
harness = false harness = false
required-features = ["lsp-test-util"] required-features = ["lsp-test-util"]
[[bench]]
name = "executor_benchmark_criterion"
harness = false
[[bench]] [[bench]]
name = "benchmark_kcl_samples" name = "benchmark_kcl_samples"
harness = false harness = false

View File

@ -4,7 +4,6 @@ use std::{
}; };
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Criterion};
use tokio::runtime::Runtime;
const IGNORE_DIRS: [&str; 2] = ["step", "screenshots"]; const IGNORE_DIRS: [&str; 2] = ["step", "screenshots"];
@ -46,7 +45,7 @@ fn run_benchmarks(c: &mut Criterion) {
let benchmark_dirs = discover_benchmark_dirs(&base_dir); let benchmark_dirs = discover_benchmark_dirs(&base_dir);
let rt = Runtime::new().unwrap(); let rt = tokio::runtime::Runtime::new().unwrap();
for dir in benchmark_dirs { for dir in benchmark_dirs {
let dir_name = dir.file_name().unwrap().to_string_lossy().to_string(); let dir_name = dir.file_name().unwrap().to_string_lossy().to_string();
@ -70,20 +69,21 @@ fn run_benchmarks(c: &mut Criterion) {
let program = kcl_lib::Program::parse_no_errs(&input_content).unwrap(); let program = kcl_lib::Program::parse_no_errs(&input_content).unwrap();
group.bench_function("parse", |b| { group.bench_function(format!("parse_{}", dir_name), |b| {
b.iter(|| kcl_lib::Program::parse_no_errs(black_box(&input_content)).unwrap()) b.iter(|| kcl_lib::Program::parse_no_errs(black_box(&input_content)).unwrap())
}); });
group.bench_function("execute", |b| { group.bench_function(format!("execute_{}", dir_name), |b| {
b.iter(|| { b.iter(|| {
rt.block_on(async { if let Err(err) = rt.block_on(async {
let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()) let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()).await?;
.await
.unwrap();
let mut exec_state = kcl_lib::ExecState::new(&ctx.settings); let mut exec_state = kcl_lib::ExecState::new(&ctx.settings);
ctx.run(black_box(&program), &mut exec_state).await.unwrap(); ctx.run(black_box(&program), &mut exec_state).await?;
ctx.close().await; ctx.close().await;
}) Ok::<(), anyhow::Error>(())
}) {
panic!("Failed to execute program: {}", err);
}
}) })
}); });

View File

@ -1,63 +0,0 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use kcl_lib::{test_server, UnitLength::Mm};
use tokio::runtime::Runtime;
pub fn bench_execute(c: &mut Criterion) {
for (name, code) in [
("big_kitt", KITT_PROGRAM),
("cube", CUBE_PROGRAM),
("server_rack_lite", SERVER_RACK_LITE_PROGRAM),
("server_rack_heavy", SERVER_RACK_HEAVY_PROGRAM),
("lsystem", LSYSTEM_PROGRAM),
] {
let mut group = c.benchmark_group("executor");
// Configure Criterion.rs to detect smaller differences and increase sample size to improve
// precision and counteract the resulting noise.
group
.sample_size(10)
.measurement_time(std::time::Duration::from_secs(1)); // Short
// measurement
// time to keep
// it from
// running in
// parallel
group.bench_with_input(BenchmarkId::new("execute", name), &code, |b, &s| {
let rt = Runtime::new().unwrap();
// Spawn a future onto the runtime
b.iter(|| {
rt.block_on(test_server::execute_and_snapshot(s, Mm, None)).unwrap();
});
});
group.finish();
}
}
pub fn bench_lego(c: &mut Criterion) {
let mut group = c.benchmark_group("executor_lego_pattern");
// Configure Criterion.rs to detect smaller differences and increase sample size to improve
// precision and counteract the resulting noise.
group.sample_size(10);
// Create lego bricks with N x 10 bumps, where N is each element of `sizes`.
let sizes = vec![1, 2, 4];
for size in sizes {
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
let rt = Runtime::new().unwrap();
let code = LEGO_PROGRAM.replace("{{N}}", &size.to_string());
// Spawn a future onto the runtime
b.iter(|| {
rt.block_on(test_server::execute_and_snapshot(&code, Mm, None)).unwrap();
});
});
}
group.finish();
}
criterion_group!(benches, bench_lego, bench_execute);
criterion_main!(benches);
const KITT_PROGRAM: &str = include_str!("../e2e/executor/inputs/kittycad_svg.kcl");
const CUBE_PROGRAM: &str = include_str!("../e2e/executor/inputs/cube.kcl");
const SERVER_RACK_HEAVY_PROGRAM: &str = include_str!("../e2e/executor/inputs/server-rack-heavy.kcl");
const SERVER_RACK_LITE_PROGRAM: &str = include_str!("../e2e/executor/inputs/server-rack-lite.kcl");
const LEGO_PROGRAM: &str = include_str!("../e2e/executor/inputs/slow_lego.kcl.tmpl");
const LSYSTEM_PROGRAM: &str = include_str!("../e2e/executor/inputs/lsystem.kcl");

View File

@ -1,8 +1,15 @@
const part001 = startSketchOn('XY') startProfileAt([0, 0], startSketchOn("XY"))
|> startProfileAt([0,0], %) |> xLine(length = 10, tag = $line000)
|> line(end = [0, 10], tag = $thing) |> yLine(length = 10, tag = $line001)
|> line(end = [10, 0]) |> xLine(endAbsolute = profileStartX(%), tag = $line002)
|> line(end = [0, -10], tag = $thing2) |> close(tag = $line003)
|> close() |> extrude(length = 10)
|> extrude(length = 10) |> fillet(
|> fillet(radius = 0.5, tags = [thing, thing]) radius = 1,
tags = [
line003,
getNextAdjacentEdge(line000),
getPreviousAdjacentEdge(line001)
],
)

View File

@ -27,11 +27,14 @@ async fn kcl_test_fillet_duplicate_tags() {
let code = kcl_input!("fillet_duplicate_tags"); let code = kcl_input!("fillet_duplicate_tags");
let result = execute_and_snapshot(code, UnitLength::Mm, None).await; let result = execute_and_snapshot(code, UnitLength::Mm, None).await;
assert!(result.is_err()); let err = result.expect_err("Code should have failed due to the duplicate edges being filletted");
let err = err.as_kcl_error().unwrap();
assert_eq!( assert_eq!(
result.err().unwrap().to_string(), err.message(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([229, 272, 0])], message: "Duplicate tags are not allowed." }"#, "The same edge ID is being referenced multiple times, which is not allowed. Please select a different edge"
); );
assert_eq!(err.source_ranges().len(), 2);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -857,7 +860,7 @@ part = rectShape([0, 0], 20, 20)
}; };
assert_eq!( assert_eq!(
err.error.message(), err.error.message(),
"This function expected this argument to be of type SketchOrSurface but it's actually of type string (text)" "This function expected the input argument to be of type SketchOrSurface but it's actually of type string (text)"
); );
} }
@ -2103,7 +2106,7 @@ async fn kcl_test_better_type_names() {
}, },
None => todo!(), None => todo!(),
}; };
assert_eq!(err, "This function expected this argument to be of type SolidSet but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`"); assert_eq!(err, "This function expected the input argument to be of type SolidSet but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`");
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]

View File

@ -21,7 +21,7 @@ use crate::{
}; };
const TYPES_DIR: &str = "../../docs/kcl/types"; const TYPES_DIR: &str = "../../docs/kcl/types";
const LANG_TOPICS: [&str; 4] = ["Types", "Modules", "Settings", "Known Issues"]; const LANG_TOPICS: [&str; 5] = ["Types", "Modules", "Settings", "Known Issues", "Constants"];
// These types are declared in std. // These types are declared in std.
const DECLARED_TYPES: [&str; 7] = ["number", "string", "tag", "bool", "Sketch", "Solid", "Plane"]; const DECLARED_TYPES: [&str; 7] = ["number", "string", "tag", "bool", "Sketch", "Solid", "Plane"];
@ -298,6 +298,7 @@ fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
hbs.register_template_string("propertyType", include_str!("templates/propertyType.hbs"))?; hbs.register_template_string("propertyType", include_str!("templates/propertyType.hbs"))?;
hbs.register_template_string("schema", include_str!("templates/schema.hbs"))?; hbs.register_template_string("schema", include_str!("templates/schema.hbs"))?;
hbs.register_template_string("index", include_str!("templates/index.hbs"))?; hbs.register_template_string("index", include_str!("templates/index.hbs"))?;
hbs.register_template_string("consts-index", include_str!("templates/consts-index.hbs"))?;
hbs.register_template_string("function", include_str!("templates/function.hbs"))?; hbs.register_template_string("function", include_str!("templates/function.hbs"))?;
hbs.register_template_string("const", include_str!("templates/const.hbs"))?; hbs.register_template_string("const", include_str!("templates/const.hbs"))?;
hbs.register_template_string("type", include_str!("templates/type.hbs"))?; hbs.register_template_string("type", include_str!("templates/type.hbs"))?;
@ -312,6 +313,9 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
let mut functions = HashMap::new(); let mut functions = HashMap::new();
functions.insert("std".to_owned(), Vec::new()); functions.insert("std".to_owned(), Vec::new());
let mut constants = HashMap::new();
constants.insert("std".to_owned(), Vec::new());
for key in combined.keys() { for key in combined.keys() {
let internal_fn = combined let internal_fn = combined
.get(key) .get(key)
@ -337,6 +341,13 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
DocData::Const(c) => (c.name.clone(), d.file_name()), DocData::Const(c) => (c.name.clone(), d.file_name()),
DocData::Ty(t) => (t.name.clone(), d.file_name()), DocData::Ty(t) => (t.name.clone(), d.file_name()),
}); });
if let DocData::Const(c) = d {
constants
.entry(d.mod_name())
.or_default()
.push((c.name.clone(), d.file_name()));
}
} }
// TODO we should sub-divide into types, constants, and functions. // TODO we should sub-divide into types, constants, and functions.
@ -362,7 +373,7 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
.map(|name| { .map(|name| {
json!({ json!({
"name": name, "name": name,
"file_name": name.to_lowercase().replace(' ', "-"), "file_name": name.to_lowercase().replace(' ', "-").replace("constants", "consts"),
}) })
}) })
.collect(); .collect();
@ -375,6 +386,31 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
expectorate::assert_contents("../../docs/kcl/index.md", &output); expectorate::assert_contents("../../docs/kcl/index.md", &output);
// Generate the index for the constants.
let mut sorted_consts: Vec<_> = constants
.into_iter()
.map(|(m, mut consts)| {
consts.sort();
let val = json!({
"name": m,
"consts": consts.into_iter().map(|(n, f)| json!({
"name": n,
"file_name": f,
})).collect::<Vec<_>>(),
});
(m, val)
})
.collect();
sorted_consts.sort_by(|t1, t2| t1.0.cmp(&t2.0));
let data: Vec<_> = sorted_consts.into_iter().map(|(_, val)| val).collect();
let data = json!({
"consts": data,
});
let output = hbs.render("consts-index", &data)?;
expectorate::assert_contents("../../docs/kcl/consts.md", &output);
Ok(()) Ok(())
} }
@ -405,7 +441,7 @@ fn generate_example(index: usize, src: &str, props: &ExampleProperties, file_nam
})) }))
} }
fn generate_type_from_kcl(ty: &TyData, file_name: String) -> Result<()> { fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String) -> Result<()> {
if ty.properties.doc_hidden { if ty.properties.doc_hidden {
return Ok(()); return Ok(());
} }
@ -416,7 +452,7 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String) -> Result<()> {
.examples .examples
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &file_name)) .filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &example_name))
.collect(); .collect();
let data = json!({ let data = json!({
@ -428,7 +464,7 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String) -> Result<()> {
}); });
let output = hbs.render("kclType", &data)?; let output = hbs.render("kclType", &data)?;
expectorate::assert_contents(format!("../../docs/kcl/types/{}.md", file_name), &output); expectorate::assert_contents(format!("../../docs/kcl/{}.md", file_name), &output);
Ok(()) Ok(())
} }
@ -480,7 +516,7 @@ fn generate_function_from_kcl(function: &FnData, file_name: String) -> Result<()
Ok(()) Ok(())
} }
fn generate_const_from_kcl(cnst: &ConstData, file_name: String) -> Result<()> { fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: String) -> Result<()> {
if cnst.properties.doc_hidden { if cnst.properties.doc_hidden {
return Ok(()); return Ok(());
} }
@ -490,7 +526,7 @@ fn generate_const_from_kcl(cnst: &ConstData, file_name: String) -> Result<()> {
.examples .examples
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &file_name)) .filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &example_name))
.collect(); .collect();
let data = json!({ let data = json!({
@ -1028,8 +1064,8 @@ fn test_generate_stdlib_markdown_docs() {
for d in &kcl_std { for d in &kcl_std {
match d { match d {
DocData::Fn(f) => generate_function_from_kcl(f, d.file_name()).unwrap(), DocData::Fn(f) => generate_function_from_kcl(f, d.file_name()).unwrap(),
DocData::Const(c) => generate_const_from_kcl(c, d.file_name()).unwrap(), DocData::Const(c) => generate_const_from_kcl(c, d.file_name(), d.example_name()).unwrap(),
DocData::Ty(t) => generate_type_from_kcl(t, d.file_name()).unwrap(), DocData::Ty(t) => generate_type_from_kcl(t, d.file_name(), d.example_name()).unwrap(),
} }
} }
} }
@ -1061,7 +1097,8 @@ fn test_generate_stdlib_json_schema() {
async fn test_code_in_topics() { async fn test_code_in_topics() {
let mut join_set = JoinSet::new(); let mut join_set = JoinSet::new();
for name in LANG_TOPICS { for name in LANG_TOPICS {
let filename = format!("../../docs/kcl/{}.md", name.to_lowercase().replace(' ', "-")); let filename =
format!("../../docs/kcl/{}.md", name.to_lowercase().replace(' ', "-")).replace("constants", "consts");
let mut file = File::open(&filename).unwrap(); let mut file = File::open(&filename).unwrap();
let mut text = String::new(); let mut text = String::new();
file.read_to_string(&mut text).unwrap(); file.read_to_string(&mut text).unwrap();

View File

@ -116,10 +116,18 @@ impl DocData {
#[allow(dead_code)] #[allow(dead_code)]
pub fn file_name(&self) -> String { pub fn file_name(&self) -> String {
match self {
DocData::Fn(f) => f.qual_name.replace("::", "-"),
DocData::Const(c) => format!("consts/{}", c.qual_name.replace("::", "-")),
DocData::Ty(t) => format!("types/{}", t.name.clone()),
}
}
#[allow(dead_code)]
pub fn example_name(&self) -> String {
match self { match self {
DocData::Fn(f) => f.qual_name.replace("::", "-"), DocData::Fn(f) => f.qual_name.replace("::", "-"),
DocData::Const(c) => format!("const_{}", c.qual_name.replace("::", "-")), DocData::Const(c) => format!("const_{}", c.qual_name.replace("::", "-")),
// TODO might want to change this
DocData::Ty(t) => t.name.clone(), DocData::Ty(t) => t.name.clone(),
} }
} }
@ -872,7 +880,7 @@ mod test {
Ok(img) => img, Ok(img) => img,
}; };
twenty_twenty::assert_image( twenty_twenty::assert_image(
format!("tests/outputs/serial_test_example_{}{i}.png", d.file_name()), format!("tests/outputs/serial_test_example_{}{i}.png", d.example_name()),
&result, &result,
0.99, 0.99,
); );

View File

@ -133,6 +133,7 @@ impl StdLibFnArg {
|| self.type_ == "SolidSet" || self.type_ == "SolidSet"
|| self.type_ == "SketchSurface" || self.type_ == "SketchSurface"
|| self.type_ == "SketchOrSurface" || self.type_ == "SketchOrSurface"
|| self.type_ == "SolidOrImportedGeometry"
{ {
return Ok(Some((index, format!("{label}${{{}:{}}}", index, "%")))); return Ok(Some((index, format!("{label}${{{}:{}}}", index, "%"))));
} else if (self.type_ == "TagDeclarator" || self.type_ == "TagNode") && self.required { } else if (self.type_ == "TagDeclarator" || self.type_ == "TagNode") && self.required {

View File

@ -0,0 +1,17 @@
---
title: "KCL Constants"
excerpt: "Documentation for the KCL constants."
layout: manual
---
## Table of Contents
{{#each consts}}
### `{{name}}`
{{#each consts}}
- [`{{name}}`](/docs/kcl/{{file_name}})
{{/each}}
{{/each}}

View File

@ -228,6 +228,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
cmd: &ModelingCmd, cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> { ) -> Result<(), crate::errors::KclError> {
// In isolated mode, we don't send the command to the engine. // In isolated mode, we don't send the command to the engine.
//
// Note: It's important to allow commands through for the mock engine
// because it needs the commands to build the artifact graph in sketch
// mode.
if self.execution_kind().await.is_isolated() { if self.execution_kind().await.is_isolated() {
return Ok(()); return Ok(());
} }
@ -253,6 +257,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
cmd: &ModelingCmd, cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> { ) -> Result<(), crate::errors::KclError> {
// In isolated mode, we don't send the command to the engine. // In isolated mode, we don't send the command to the engine.
//
// Note: It's important to allow commands through for the mock engine
// because it needs the commands to build the artifact graph in sketch
// mode.
if self.execution_kind().await.is_isolated() { if self.execution_kind().await.is_isolated() {
return Ok(()); return Ok(());
} }

View File

@ -48,6 +48,15 @@ impl ExecErrorWithState {
} }
} }
impl ExecError {
pub fn as_kcl_error(&self) -> Option<&crate::KclError> {
let ExecError::Kcl(k) = &self else {
return None;
};
Some(&k.error)
}
}
impl From<ExecError> for ExecErrorWithState { impl From<ExecError> for ExecErrorWithState {
fn from(error: ExecError) -> Self { fn from(error: ExecError) -> Self {
Self { Self {
@ -169,13 +178,8 @@ impl KclErrorWithOutputs {
path: self path: self
.filenames .filenames
.get(&first_source_range.module_id()) .get(&first_source_range.module_id())
.ok_or_else(|| { .cloned()
anyhow::anyhow!( .unwrap_or(ModulePath::Main),
"Could not find filename for module id: {:?}",
first_source_range.module_id()
)
})?
.clone(),
}); });
let filename = source.path.to_string(); let filename = source.path.to_string();
let kcl_source = source.source.to_string(); let kcl_source = source.source.to_string();
@ -183,11 +187,10 @@ impl KclErrorWithOutputs {
let mut related = Vec::new(); let mut related = Vec::new();
for source_range in source_ranges { for source_range in source_ranges {
let module_id = source_range.module_id(); let module_id = source_range.module_id();
let source = self let source = self.source_files.get(&module_id).cloned().unwrap_or(ModuleSource {
.source_files source: code.to_string(),
.get(&module_id) path: self.filenames.get(&module_id).cloned().unwrap_or(ModulePath::Main),
.cloned() });
.ok_or_else(|| anyhow::anyhow!("Could not find source file for module id: {:?}", module_id))?;
let error = self.error.override_source_ranges(vec![source_range]); let error = self.error.override_source_ranges(vec![source_range]);
let report = Report { let report = Report {
error, error,

View File

@ -488,6 +488,11 @@ impl ArtifactGraph {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.map.len() self.map.len()
} }
#[cfg(test)]
pub(crate) fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
self.map.iter()
}
} }
pub(super) fn build_artifact_graph( pub(super) fn build_artifact_graph(
@ -622,10 +627,7 @@ fn artifacts_to_update(
let uuid = artifact_command.cmd_id; let uuid = artifact_command.cmd_id;
let id = ArtifactId::new(uuid); let id = ArtifactId::new(uuid);
let Some(response) = responses.get(&uuid) else { let response = responses.get(&uuid);
// Response not found or not successful.
return Ok(Vec::new());
};
let cmd = &artifact_command.command; let cmd = &artifact_command.command;
@ -757,7 +759,7 @@ fn artifacts_to_update(
new_path.seg_ids = vec![id]; new_path.seg_ids = vec![id];
return_arr.push(Artifact::Path(new_path)); return_arr.push(Artifact::Path(new_path));
} }
if let OkModelingCmdResponse::ClosePath(close_path) = response { if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
return_arr.push(Artifact::Solid2d(Solid2d { return_arr.push(Artifact::Solid2d(Solid2d {
id: close_path.face_id.into(), id: close_path.face_id.into(),
path_id, path_id,
@ -800,7 +802,7 @@ fn artifacts_to_update(
return Ok(return_arr); return Ok(return_arr);
} }
ModelingCmd::Loft(loft_cmd) => { ModelingCmd::Loft(loft_cmd) => {
let OkModelingCmdResponse::Loft(_) = response else { let Some(OkModelingCmdResponse::Loft(_)) = response else {
return Ok(Vec::new()); return Ok(Vec::new());
}; };
let mut return_arr = Vec::new(); let mut return_arr = Vec::new();
@ -830,7 +832,7 @@ fn artifacts_to_update(
return Ok(return_arr); return Ok(return_arr);
} }
ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => { ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
let OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info) = response else { let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
return Ok(Vec::new()); return Ok(Vec::new());
}; };
let mut return_arr = Vec::new(); let mut return_arr = Vec::new();
@ -954,6 +956,11 @@ fn artifacts_to_update(
ModelingCmd::Solid3dGetOppositeEdge(_) => SweepEdgeSubType::Opposite, ModelingCmd::Solid3dGetOppositeEdge(_) => SweepEdgeSubType::Opposite,
_ => unreachable!(), _ => unreachable!(),
}; };
// We need a response to continue. If we're in sketch mode doing
// mock execution, we won't have one.
if response.is_none() {
return Ok(Vec::new());
}
let face_id = ArtifactId::new(*face_id); let face_id = ArtifactId::new(*face_id);
let edge_id = ArtifactId::new(*edge_id); let edge_id = ArtifactId::new(*edge_id);
let Some(Artifact::Wall(wall)) = artifacts.get(&face_id) else { let Some(Artifact::Wall(wall)) = artifacts.get(&face_id) else {
@ -969,7 +976,7 @@ fn artifacts_to_update(
return Ok(Vec::new()); return Ok(Vec::new());
}; };
let response_edge_id = match response { let response_edge_id = match response {
OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(r) => { Some(OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(r)) => {
let Some(edge_id) = r.edge else { let Some(edge_id) = r.edge else {
return Err(KclError::Internal(KclErrorDetails { return Err(KclError::Internal(KclErrorDetails {
message:format!( message:format!(
@ -980,7 +987,7 @@ fn artifacts_to_update(
}; };
edge_id.into() edge_id.into()
} }
OkModelingCmdResponse::Solid3dGetOppositeEdge(r) => r.edge.into(), Some(OkModelingCmdResponse::Solid3dGetOppositeEdge(r)) => r.edge.into(),
_ => { _ => {
return Err(KclError::Internal(KclErrorDetails { return Err(KclError::Internal(KclErrorDetails {
message:format!( message:format!(

View File

@ -5,6 +5,7 @@ use std::sync::Arc;
use itertools::{EitherOrBoth, Itertools}; use itertools::{EitherOrBoth, Itertools};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use super::IdGenerator;
use crate::{ use crate::{
execution::{annotations, memory::Stack, EnvironmentRef, ExecState, ExecutorSettings}, execution::{annotations, memory::Stack, EnvironmentRef, ExecState, ExecutorSettings},
parsing::ast::types::{Annotation, Node, Program}, parsing::ast::types::{Annotation, Node, Program},
@ -14,8 +15,10 @@ use crate::{
lazy_static::lazy_static! { lazy_static::lazy_static! {
/// A static mutable lock for updating the last successful execution state for the cache. /// A static mutable lock for updating the last successful execution state for the cache.
static ref OLD_AST: Arc<RwLock<Option<OldAstState>>> = Default::default(); static ref OLD_AST: Arc<RwLock<Option<OldAstState>>> = Default::default();
// The last successful run's memory. Not cleared after an unssuccessful run. // The last successful run's memory. Not cleared after an unsuccessful run.
static ref PREV_MEMORY: Arc<RwLock<Option<Stack>>> = Default::default(); static ref PREV_MEMORY: Arc<RwLock<Option<Stack>>> = Default::default();
/// The ID generator for mock execution.
static ref MOCK_ID_GENERATOR: Arc<RwLock<Option<IdGenerator>>> = Default::default();
} }
/// Read the old ast memory from the lock. /// Read the old ast memory from the lock.
@ -49,6 +52,21 @@ pub async fn clear_mem_cache() {
*old_mem = None; *old_mem = None;
} }
pub(crate) async fn read_mock_ids() -> Option<IdGenerator> {
let cache = MOCK_ID_GENERATOR.read().await;
cache.clone()
}
pub(super) async fn write_mock_ids(id_gen: IdGenerator) {
let mut cache = MOCK_ID_GENERATOR.write().await;
*cache = Some(id_gen);
}
pub async fn clear_mock_ids() {
let mut cache = MOCK_ID_GENERATOR.write().await;
*cache = None;
}
/// Information for the caching an AST and smartly re-executing it if we can. /// Information for the caching an AST and smartly re-executing it if we can.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CacheInformation<'a> { pub struct CacheInformation<'a> {

View File

@ -544,12 +544,11 @@ impl ExecutorContext {
self.exec_module_for_result(module_id, exec_state, ExecutionKind::Normal, metadata.source_range) self.exec_module_for_result(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
.await? .await?
.unwrap_or_else(|| { .unwrap_or_else(|| {
// The module didn't have a return value. Currently, exec_state.warn(CompilationError::err(
// the only way to have a return value is with the final metadata.source_range,
// statement being an expression statement. "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
// ));
// TODO: Make a warning when we support them in the
// execution phase.
let mut new_meta = vec![metadata.to_owned()]; let mut new_meta = vec![metadata.to_owned()];
new_meta.extend(meta); new_meta.extend(meta);
KclValue::KclNone { KclValue::KclNone {
@ -1135,7 +1134,7 @@ impl Node<CallExpressionKw> {
}, },
self.into(), self.into(),
ctx.clone(), ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic), exec_state.mod_local.pipe_value.clone().map(|v| Arg::new(v, callsite)),
); );
match ctx.stdlib.get_either(fn_name) { match ctx.stdlib.get_either(fn_name) {
FunctionKind::Core(func) => { FunctionKind::Core(func) => {
@ -1297,7 +1296,7 @@ impl Node<CallExpression> {
fn_args, fn_args,
self.into(), self.into(),
ctx.clone(), ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic), exec_state.mod_local.pipe_value.clone().map(|v| Arg::new(v, callsite)),
); );
let mut return_value = { let mut return_value = {
// Don't early-return in this block. // Don't early-return in this block.
@ -1948,7 +1947,11 @@ impl FunctionSource {
args, args,
source_range, source_range,
ctx.clone(), ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic), exec_state
.mod_local
.pipe_value
.clone()
.map(|v| Arg::new(v, source_range)),
); );
func(exec_state, args).await.map(Some) func(exec_state, args).await.map(Some)

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