Compare commits

..

1 Commits

Author SHA1 Message Date
8424355a07 xstate v5 rough start 2024-02-19 20:51:29 +11:00
244 changed files with 6189 additions and 15569 deletions

View File

@ -3,3 +3,4 @@ VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000
VITE_KC_SENTRY_DSN=

View File

@ -3,3 +3,4 @@ VITE_KC_API_BASE_URL=https://api.zoo.dev
VITE_KC_SITE_BASE_URL=https://zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000
VITE_KC_SENTRY_DSN=https://a814f2f66734989a90367f48feee28ca@o1042111.ingest.sentry.io/4505789425844224

View File

@ -1,85 +0,0 @@
name: Bug Report
description: File a bug report for the Zoo Modeling App
title: "[BUG]: "
labels: ["bug"]
assignees: []
body:
- type: markdown
attributes:
value: "Thank you for taking the time to report a bug. Please provide as much information as possible to help us resolve it."
- type: textarea
id: describe-bug
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
placeholder: "Explain the bug..."
validations:
required: true
- type: textarea
id: reproduce-bug
attributes:
label: Steps to Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
description: Description of what you expected to happen.
placeholder: "I expected that..."
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots and Recordings
description: If applicable, add screenshots to help explain your problem. Maximum upload size is 10MB.
placeholder: "You can attach images or video recordings here."
validations:
required: false
- type: input
id: desktop-os
attributes:
label: Desktop OS
description: "Your operating system"
placeholder: "example: Windows 10, MacOS Big Sur"
validations:
required: true
- type: input
id: browser
attributes:
label: Browser
description: "If you are using the web version, please specify the browser you are using."
placeholder: "example: Chrome, Safari"
validations:
required: false
- type: input
id: version
attributes:
label: Version
description: "The version of the Zoo Modeling App you're using."
placeholder: "example: v0.15.0. You can find this in the settings."
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Add any other context about the problem here.
placeholder: "Anything else you want to add..."
validations:
required: false

29
.github/workflows/announce_release.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Announce Release
on:
release:
types: [published]
jobs:
announce_release:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
- name: Announce Release
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
RELEASE_VERSION: ${{ github.event.release.tag_name }}
RELEASE_BODY: ${{ github.event.release.body}}
run: python public/announce_release.py

View File

@ -40,20 +40,6 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install vector
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 &
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: taiki-e/install-action@nextest
- name: Rust Cache
@ -62,7 +48,7 @@ jobs:
shell: bash
run: |-
cd "${{ matrix.dir }}"
cargo nextest run --workspace --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
cargo nextest run --workspace --no-fail-fast -P ci
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000

View File

@ -86,6 +86,8 @@ jobs:
- run: yarn test:nowatch
- run: yarn test:cov
prepare-json-files:
runs-on: ubuntu-latest # seperate job on Ubuntu for easy string manipulations (compared to Windows)
@ -336,7 +338,7 @@ jobs:
cat last_download.json
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.2'
uses: 'google-github-actions/auth@v2.1.1'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
@ -370,28 +372,3 @@ jobs:
uses: softprops/action-gh-release@v1
with:
files: 'artifact/*/Zoo*'
announce_release:
needs: [publish-apps-release]
runs-on: ubuntu-latest
if: github.event_name == 'release'
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
- name: Announce Release
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
RELEASE_VERSION: ${{ github.event.release.tag_name }}
RELEASE_BODY: ${{ github.event.release.body}}
run: python public/announce_release.py

View File

@ -4,11 +4,6 @@ on:
branches: [ main ]
pull_request:
branches: [ main ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
playwright-ubuntu:
timeout-minutes: 60
@ -19,7 +14,7 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- uses: KittyCAD/action-install-cli@v0.2.21
- uses: KittyCAD/action-install-cli@v0.2.16
- name: Install dependencies
run: yarn
- name: Install Playwright Browsers
@ -85,6 +80,7 @@ jobs:
playwright-macos:
timeout-minutes: 60
runs-on: macos-14
needs: playwright-ubuntu
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4

View File

@ -1,21 +0,0 @@
[sources.github-actions-file]
type = "file"
data_dir = "/tmp/vector"
include = ["/tmp/github-actions.log"]
# Modify the logs to include the action name.
[transforms.add-action-name]
type = "remap"
inputs = [ "github-actions-file" ]
source = '''
.action = "GITHUB_WORKFLOW"
.repo = "GITHUB_REPOSITORY"
.sha = "GITHUB_SHA"
.ref = "GITHUB_REF_NAME"
'''
[sinks.axiom]
type = "axiom"
inputs = ["add-action-name"]
token = "GH_ACTIONS_AXIOM_TOKEN"
dataset = "github-actions"

3
.gitignore vendored
View File

@ -33,7 +33,6 @@ src/wasm-lib/bindings
src/wasm-lib/kcl/bindings
public/wasm_lib_bg.wasm
src/wasm-lib/lcov.info
src/wasm-lib/grackle/test_json_output
e2e/playwright/playwright-secrets.env
e2e/playwright/temp1.png
@ -55,5 +54,3 @@ e2e/playwright/export-snapshots/*embedded.gltf
## generated files
src/**/*.typegen.ts
src/wasm-lib/grackle/stdlib_cube_partial.json

View File

@ -136,11 +136,6 @@ Before you submit a contribution PR to this repo, please ensure that:
VERSION=x.y.z yarn run bump-jsons
```
Alternatively you can try the experimental `make-release.sh` bash script that will create the branch with the updated json files for you.
run `./make-release.sh` for a patch update
run `./make-release.sh "minor"` for minor
run `./make-release.sh "major"` for major
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and past in the following
```typescript

View File

@ -6,9 +6,11 @@ once fixed in engine will just start working here with no language changes.
- **Sketch on Face**: If your sketch is outside the edges of the face (on which you
are sketching) you will get multiple models returned instead of one single
model for that sketch and its underlying 3D object.
If you see a red line around your model, it means this is happening.
- **Patterns**: If you try and pass a pattern to `hole` currently only the first
item in the pattern is being subtracted. This is an engine bug that is being
worked on.
- **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
currently move or transform the imported objects at all, once we have assemblies
this will work.
you cannot edit it. You also cannot move or transform the imported objects at
all. In the future, after v1, the engine will account for this.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 80 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -19,59 +19,59 @@ DATA;
);
#4 = CARTESIAN_POINT('NONE', (0, 0, -0));
#5 = VERTEX_POINT('NONE', #4);
#6 = CARTESIAN_POINT('NONE', (0, -0.64516, -0));
#6 = CARTESIAN_POINT('NONE', (0, -0.0254, -0));
#7 = VERTEX_POINT('NONE', #6);
#8 = CARTESIAN_POINT('NONE', (0, -0.64516, 2.58064));
#8 = CARTESIAN_POINT('NONE', (0, -0.0254, 0.1016));
#9 = VERTEX_POINT('NONE', #8);
#10 = CARTESIAN_POINT('NONE', (0, 0, 2.58064));
#10 = CARTESIAN_POINT('NONE', (0, 0, 0.1016));
#11 = VERTEX_POINT('NONE', #10);
#12 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0));
#12 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0));
#13 = VERTEX_POINT('NONE', #12);
#14 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, 2.58064));
#14 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, 0.1016));
#15 = VERTEX_POINT('NONE', #14);
#16 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0));
#16 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0));
#17 = VERTEX_POINT('NONE', #16);
#18 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, 2.58064));
#18 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, 0.1016));
#19 = VERTEX_POINT('NONE', #18);
#20 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0));
#20 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0));
#21 = VERTEX_POINT('NONE', #20);
#22 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, 2.58064));
#22 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, 0.1016));
#23 = VERTEX_POINT('NONE', #22);
#24 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0));
#24 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0));
#25 = VERTEX_POINT('NONE', #24);
#26 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, 2.58064));
#26 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, 0.1016));
#27 = VERTEX_POINT('NONE', #26);
#28 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0));
#28 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0));
#29 = VERTEX_POINT('NONE', #28);
#30 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, 2.58064));
#30 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, 0.1016));
#31 = VERTEX_POINT('NONE', #30);
#32 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0));
#32 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0));
#33 = VERTEX_POINT('NONE', #32);
#34 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, 2.58064));
#34 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, 0.1016));
#35 = VERTEX_POINT('NONE', #34);
#36 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0));
#36 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0));
#37 = VERTEX_POINT('NONE', #36);
#38 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, 2.58064));
#38 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, 0.1016));
#39 = VERTEX_POINT('NONE', #38);
#40 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0));
#40 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0));
#41 = VERTEX_POINT('NONE', #40);
#42 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, 2.58064));
#42 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, 0.1016));
#43 = VERTEX_POINT('NONE', #42);
#44 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0));
#44 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0));
#45 = VERTEX_POINT('NONE', #44);
#46 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, 2.58064));
#46 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, 0.1016));
#47 = VERTEX_POINT('NONE', #46);
#48 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0));
#48 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0));
#49 = VERTEX_POINT('NONE', #48);
#50 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, 2.58064));
#50 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, 0.1016));
#51 = VERTEX_POINT('NONE', #50);
#52 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0));
#52 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0));
#53 = VERTEX_POINT('NONE', #52);
#54 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, 2.58064));
#54 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, 0.1016));
#55 = VERTEX_POINT('NONE', #54);
#56 = CARTESIAN_POINT('NONE', (0, 0.64516, -0));
#56 = CARTESIAN_POINT('NONE', (0, 0.0254, -0));
#57 = VERTEX_POINT('NONE', #56);
#58 = CARTESIAN_POINT('NONE', (0, 0.64516, 2.58064));
#58 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016));
#59 = VERTEX_POINT('NONE', #58);
#60 = DIRECTION('NONE', (0, -1, 0));
#61 = VECTOR('NONE', #60, 1);
@ -79,11 +79,11 @@ DATA;
#63 = LINE('NONE', #62, #61);
#64 = DIRECTION('NONE', (0, 0, 1));
#65 = VECTOR('NONE', #64, 1);
#66 = CARTESIAN_POINT('NONE', (0, -0.64516, -0));
#66 = CARTESIAN_POINT('NONE', (0, -0.0254, -0));
#67 = LINE('NONE', #66, #65);
#68 = DIRECTION('NONE', (0, -1, 0));
#69 = VECTOR('NONE', #68, 1);
#70 = CARTESIAN_POINT('NONE', (0, 0, 2.58064));
#70 = CARTESIAN_POINT('NONE', (0, 0, 0.1016));
#71 = LINE('NONE', #70, #69);
#72 = DIRECTION('NONE', (0, 0, 1));
#73 = VECTOR('NONE', #72, 1);
@ -91,155 +91,155 @@ DATA;
#75 = LINE('NONE', #74, #73);
#76 = DIRECTION('NONE', (1, 0, 0));
#77 = VECTOR('NONE', #76, 1);
#78 = CARTESIAN_POINT('NONE', (0, -0.64516, -0));
#78 = CARTESIAN_POINT('NONE', (0, -0.0254, -0));
#79 = LINE('NONE', #78, #77);
#80 = DIRECTION('NONE', (0, 0, 1));
#81 = VECTOR('NONE', #80, 1);
#82 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0));
#82 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0));
#83 = LINE('NONE', #82, #81);
#84 = DIRECTION('NONE', (1, 0, 0));
#85 = VECTOR('NONE', #84, 1);
#86 = CARTESIAN_POINT('NONE', (0, -0.64516, 2.58064));
#86 = CARTESIAN_POINT('NONE', (0, -0.0254, 0.1016));
#87 = LINE('NONE', #86, #85);
#88 = DIRECTION('NONE', (0.819152044288992, -0.5735764363510459, 0));
#88 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0));
#89 = VECTOR('NONE', #88, 1);
#90 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0));
#90 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0));
#91 = LINE('NONE', #90, #89);
#92 = DIRECTION('NONE', (0, 0, 1));
#93 = VECTOR('NONE', #92, 1);
#94 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0));
#94 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0));
#95 = LINE('NONE', #94, #93);
#96 = DIRECTION('NONE', (0.819152044288992, -0.5735764363510459, 0));
#96 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0));
#97 = VECTOR('NONE', #96, 1);
#98 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, 2.58064));
#98 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, 0.1016));
#99 = LINE('NONE', #98, #97);
#100 = DIRECTION('NONE', (1, -0.00000000000000038794063361359933, 0));
#100 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0));
#101 = VECTOR('NONE', #100, 1);
#102 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0));
#102 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0));
#103 = LINE('NONE', #102, #101);
#104 = DIRECTION('NONE', (0, 0, 1));
#105 = VECTOR('NONE', #104, 1);
#106 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0));
#106 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0));
#107 = LINE('NONE', #106, #105);
#108 = DIRECTION('NONE', (1, -0.00000000000000038794063361359933, 0));
#108 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0));
#109 = VECTOR('NONE', #108, 1);
#110 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, 2.58064));
#110 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, 0.1016));
#111 = LINE('NONE', #110, #109);
#112 = DIRECTION('NONE', (0, 1, 0));
#113 = VECTOR('NONE', #112, 1);
#114 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0));
#114 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0));
#115 = LINE('NONE', #114, #113);
#116 = DIRECTION('NONE', (0, 0, 1));
#117 = VECTOR('NONE', #116, 1);
#118 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0));
#118 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0));
#119 = LINE('NONE', #118, #117);
#120 = DIRECTION('NONE', (0, 1, 0));
#121 = VECTOR('NONE', #120, 1);
#122 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, 2.58064));
#122 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, 0.1016));
#123 = LINE('NONE', #122, #121);
#124 = DIRECTION('NONE', (-1, 0, 0));
#125 = VECTOR('NONE', #124, 1);
#126 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0));
#126 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0));
#127 = LINE('NONE', #126, #125);
#128 = DIRECTION('NONE', (0, 0, 1));
#129 = VECTOR('NONE', #128, 1);
#130 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0));
#130 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0));
#131 = LINE('NONE', #130, #129);
#132 = DIRECTION('NONE', (-1, 0, 0));
#133 = VECTOR('NONE', #132, 1);
#134 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, 2.58064));
#134 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, 0.1016));
#135 = LINE('NONE', #134, #133);
#136 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
#137 = VECTOR('NONE', #136, 1);
#138 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0));
#138 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0));
#139 = LINE('NONE', #138, #137);
#140 = DIRECTION('NONE', (0, 0, 1));
#141 = VECTOR('NONE', #140, 1);
#142 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0));
#142 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0));
#143 = LINE('NONE', #142, #141);
#144 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
#145 = VECTOR('NONE', #144, 1);
#146 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, 2.58064));
#146 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, 0.1016));
#147 = LINE('NONE', #146, #145);
#148 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406992, 0));
#148 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0));
#149 = VECTOR('NONE', #148, 1);
#150 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0));
#150 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0));
#151 = LINE('NONE', #150, #149);
#152 = DIRECTION('NONE', (0, 0, 1));
#153 = VECTOR('NONE', #152, 1);
#154 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0));
#154 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0));
#155 = LINE('NONE', #154, #153);
#156 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406992, 0));
#156 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0));
#157 = VECTOR('NONE', #156, 1);
#158 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, 2.58064));
#158 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, 0.1016));
#159 = LINE('NONE', #158, #157);
#160 = DIRECTION('NONE', (1, -0.0000000000000001378647737807002, 0));
#160 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0));
#161 = VECTOR('NONE', #160, 1);
#162 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0));
#162 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0));
#163 = LINE('NONE', #162, #161);
#164 = DIRECTION('NONE', (0, 0, 1));
#165 = VECTOR('NONE', #164, 1);
#166 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0));
#166 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0));
#167 = LINE('NONE', #166, #165);
#168 = DIRECTION('NONE', (1, -0.0000000000000001378647737807002, 0));
#168 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0));
#169 = VECTOR('NONE', #168, 1);
#170 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, 2.58064));
#170 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, 0.1016));
#171 = LINE('NONE', #170, #169);
#172 = DIRECTION('NONE', (0, 1, 0));
#173 = VECTOR('NONE', #172, 1);
#174 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0));
#174 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0));
#175 = LINE('NONE', #174, #173);
#176 = DIRECTION('NONE', (0, 0, 1));
#177 = VECTOR('NONE', #176, 1);
#178 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0));
#178 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0));
#179 = LINE('NONE', #178, #177);
#180 = DIRECTION('NONE', (0, 1, 0));
#181 = VECTOR('NONE', #180, 1);
#182 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, 2.58064));
#182 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, 0.1016));
#183 = LINE('NONE', #182, #181);
#184 = DIRECTION('NONE', (-1, 0, 0));
#185 = VECTOR('NONE', #184, 1);
#186 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0));
#186 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0));
#187 = LINE('NONE', #186, #185);
#188 = DIRECTION('NONE', (0, 0, 1));
#189 = VECTOR('NONE', #188, 1);
#190 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0));
#190 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0));
#191 = LINE('NONE', #190, #189);
#192 = DIRECTION('NONE', (-1, 0, 0));
#193 = VECTOR('NONE', #192, 1);
#194 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, 2.58064));
#194 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, 0.1016));
#195 = LINE('NONE', #194, #193);
#196 = DIRECTION('NONE', (-0.90630778703665, -0.4226182617406995, 0));
#196 = DIRECTION('NONE', (-0.90630778703665, -0.42261826174069944, 0));
#197 = VECTOR('NONE', #196, 1);
#198 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0));
#198 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0));
#199 = LINE('NONE', #198, #197);
#200 = DIRECTION('NONE', (0, 0, 1));
#201 = VECTOR('NONE', #200, 1);
#202 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0));
#202 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0));
#203 = LINE('NONE', #202, #201);
#204 = DIRECTION('NONE', (-0.90630778703665, -0.4226182617406995, 0));
#204 = DIRECTION('NONE', (-0.90630778703665, -0.42261826174069944, 0));
#205 = VECTOR('NONE', #204, 1);
#206 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, 2.58064));
#206 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, 0.1016));
#207 = LINE('NONE', #206, #205);
#208 = DIRECTION('NONE', (-1, 0, 0));
#209 = VECTOR('NONE', #208, 1);
#210 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0));
#210 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0));
#211 = LINE('NONE', #210, #209);
#212 = DIRECTION('NONE', (0, 0, 1));
#213 = VECTOR('NONE', #212, 1);
#214 = CARTESIAN_POINT('NONE', (0, 0.64516, -0));
#214 = CARTESIAN_POINT('NONE', (0, 0.0254, -0));
#215 = LINE('NONE', #214, #213);
#216 = DIRECTION('NONE', (-1, 0, 0));
#217 = VECTOR('NONE', #216, 1);
#218 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, 2.58064));
#218 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, 0.1016));
#219 = LINE('NONE', #218, #217);
#220 = DIRECTION('NONE', (0, -1, 0));
#221 = VECTOR('NONE', #220, 1);
#222 = CARTESIAN_POINT('NONE', (0, 0.64516, -0));
#222 = CARTESIAN_POINT('NONE', (0, 0.0254, -0));
#223 = LINE('NONE', #222, #221);
#224 = DIRECTION('NONE', (0, -1, 0));
#225 = VECTOR('NONE', #224, 1);
#226 = CARTESIAN_POINT('NONE', (0, 0.64516, 2.58064));
#226 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016));
#227 = LINE('NONE', #226, #225);
#228 = EDGE_CURVE('NONE', #5, #7, #63, .T.);
#229 = EDGE_CURVE('NONE', #7, #9, #67, .T.);
@ -383,59 +383,59 @@ DATA;
#367 = ORIENTED_EDGE('NONE', *, *, #267, .T.);
#368 = ORIENTED_EDGE('NONE', *, *, #269, .T.);
#369 = EDGE_LOOP('NONE', (#355, #356, #357, #358, #359, #360, #361, #362, #363, #364, #365, #366, #367, #368));
#370 = CARTESIAN_POINT('NONE', (0, -0.3225799999999985, 1.2903199999999995));
#370 = CARTESIAN_POINT('NONE', (0, -0.0127, 0.0508));
#371 = DIRECTION('NONE', (-1, -0, 0));
#372 = AXIS2_PLACEMENT_3D('NONE', #370, #371, $);
#373 = PLANE('NONE', #372);
#374 = CARTESIAN_POINT('NONE', (0.9983910612778368, -0.6451599999999998, 1.2903199999999997));
#374 = CARTESIAN_POINT('NONE', (0.039306734695977924, -0.025399999999999995, 0.0508));
#375 = DIRECTION('NONE', (0, -1, 0));
#376 = AXIS2_PLACEMENT_3D('NONE', #374, #375, $);
#377 = PLANE('NONE', #376);
#378 = CARTESIAN_POINT('NONE', (2.918166090585415, -1.2903199999999988, 1.2903199999999997));
#378 = CARTESIAN_POINT('NONE', (0.11488842876320533, -0.05079999999999996, 0.05079999999999999));
#379 = DIRECTION('NONE', (-0.5735764363510459, -0.8191520442889919, 0));
#380 = AXIS2_PLACEMENT_3D('NONE', #378, #379, $);
#381 = PLANE('NONE', #380);
#382 = CARTESIAN_POINT('NONE', (4.984285029307579, -1.9354799999999992, 1.2903199999999997));
#382 = CARTESIAN_POINT('NONE', (0.19623169406722757, -0.07619999999999999, 0.0508));
#383 = DIRECTION('NONE', (0, -1, 0));
#384 = AXIS2_PLACEMENT_3D('NONE', #382, #383, $);
#385 = PLANE('NONE', #384);
#386 = CARTESIAN_POINT('NONE', (6.129019999999999, -1.7741899999999997, 1.2903199999999997));
#386 = CARTESIAN_POINT('NONE', (0.2413, -0.06985, 0.0508));
#387 = DIRECTION('NONE', (1, -0, 0));
#388 = AXIS2_PLACEMENT_3D('NONE', #386, #387, $);
#389 = PLANE('NONE', #388);
#390 = CARTESIAN_POINT('NONE', (5.035139570965871, -1.6128999999999998, 1.2903199999999997));
#390 = CARTESIAN_POINT('NONE', (0.19823384137660915, -0.0635, 0.0508));
#391 = DIRECTION('NONE', (0, 1, -0));
#392 = AXIS2_PLACEMENT_3D('NONE', #390, #391, $);
#393 = PLANE('NONE', #392);
#394 = CARTESIAN_POINT('NONE', (2.7895291818945633, -0.8064499999999998, 1.2903199999999995));
#395 = DIRECTION('NONE', (0.5735764363510459, 0.8191520442889918, -0));
#394 = CARTESIAN_POINT('NONE', (0.10982398353915601, -0.03174999999999997, 0.0508));
#395 = DIRECTION('NONE', (0.5735764363510459, 0.8191520442889917, -0));
#396 = AXIS2_PLACEMENT_3D('NONE', #394, #395, $);
#397 = PLANE('NONE', #396);
#398 = CARTESIAN_POINT('NONE', (2.6754617854843468, 0.4838700000000003, 1.2903199999999997));
#399 = DIRECTION('NONE', (0.4226182617406992, -0.90630778703665, 0));
#398 = CARTESIAN_POINT('NONE', (0.105333141160801, 0.019049999999999987, 0.0508));
#399 = DIRECTION('NONE', (0.4226182617406993, -0.90630778703665, 0));
#400 = AXIS2_PLACEMENT_3D('NONE', #398, #399, $);
#401 = PLANE('NONE', #400);
#402 = CARTESIAN_POINT('NONE', (4.921072174555653, 0.9677399999999998, 1.2903199999999995));
#402 = CARTESIAN_POINT('NONE', (0.19374299899825406, 0.0381, 0.0508));
#403 = DIRECTION('NONE', (0, -1, 0));
#404 = AXIS2_PLACEMENT_3D('NONE', #402, #403, $);
#405 = PLANE('NONE', #404);
#406 = CARTESIAN_POINT('NONE', (6.129019999999998, 1.1290299999999989, 1.2903199999999995));
#406 = CARTESIAN_POINT('NONE', (0.2413, 0.044449999999999996, 0.0508));
#407 = DIRECTION('NONE', (1, -0, 0));
#408 = AXIS2_PLACEMENT_3D('NONE', #406, #407, $);
#409 = PLANE('NONE', #408);
#410 = CARTESIAN_POINT('NONE', (4.8853150424179725, 1.2903199999999997, 1.2903199999999997));
#410 = CARTESIAN_POINT('NONE', (0.19233523789047138, 0.0508, 0.0508));
#411 = DIRECTION('NONE', (0, 1, -0));
#412 = AXIS2_PLACEMENT_3D('NONE', #410, #411, $);
#413 = PLANE('NONE', #412);
#414 = CARTESIAN_POINT('NONE', (2.9498350424179733, 0.9677399999999998, 1.2903199999999997));
#415 = DIRECTION('NONE', (-0.42261826174069933, 0.9063077870366499, -0));
#414 = CARTESIAN_POINT('NONE', (0.11613523789047137, 0.0381, 0.05079999999999999));
#415 = DIRECTION('NONE', (-0.42261826174069966, 0.90630778703665, -0));
#416 = AXIS2_PLACEMENT_3D('NONE', #414, #415, $);
#417 = PLANE('NONE', #416);
#418 = CARTESIAN_POINT('NONE', (1.1290299999999998, 0.6451599999999998, 1.29032));
#418 = CARTESIAN_POINT('NONE', (0.044449999999999996, 0.0254, 0.0508));
#419 = DIRECTION('NONE', (0, 1, -0));
#420 = AXIS2_PLACEMENT_3D('NONE', #418, #419, $);
#421 = PLANE('NONE', #420);
#422 = CARTESIAN_POINT('NONE', (0, 0.32257999999999987, 1.2903199999999995));
#422 = CARTESIAN_POINT('NONE', (0, 0.0127, 0.0508));
#423 = DIRECTION('NONE', (-1, -0, 0));
#424 = AXIS2_PLACEMENT_3D('NONE', #422, #423, $);
#425 = PLANE('NONE', #424);
@ -443,7 +443,7 @@ DATA;
#427 = DIRECTION('NONE', (0, 0, 1));
#428 = AXIS2_PLACEMENT_3D('NONE', #426, #427, $);
#429 = PLANE('NONE', #428);
#430 = CARTESIAN_POINT('NONE', (0, 0, 2.58064));
#430 = CARTESIAN_POINT('NONE', (0, 0, 0.1016));
#431 = DIRECTION('NONE', (0, 0, 1));
#432 = AXIS2_PLACEMENT_3D('NONE', #430, #431, $);
#433 = PLANE('NONE', #432);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -1,478 +1,478 @@
solid unnamed
facet normal -1 0 0
outer loop
vertex 0 -101.600006 0
vertex 0 -4 0
vertex 0 -0 0
vertex 0 -101.600006 -25.400002
vertex 0 -4 -1
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 0 -101.600006 -25.400002
vertex 0 -4 -1
vertex 0 -0 0
vertex 0 -0 -25.400002
vertex 0 -0 -1
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0 -101.600006 -25.400002
vertex 0 -0 -25.400002
vertex 78.613464 -101.600006 -25.400002
vertex 0 -4 -1
vertex 0 -0 -1
vertex 3.0950184 -4 -1
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 78.613464 -101.600006 -25.400002
vertex 0 -0 -25.400002
vertex 78.613464 -0 -25.400002
vertex 3.0950184 -4 -1
vertex 0 -0 -1
vertex 3.0950184 -0 -1
endloop
endfacet
facet normal -0.5735764 0 -0.8191522
facet normal -0.57357645 0 -0.81915206
outer loop
vertex 78.613464 -101.600006 -25.400002
vertex 78.613464 -0 -25.400002
vertex 151.16339 -101.600006 -76.2
vertex 3.0950184 -4 -1
vertex 3.0950184 -0 -1
vertex 5.9513144 -4 -3
endloop
endfacet
facet normal -0.5735764 0 -0.8191522
facet normal -0.57357645 0 -0.81915206
outer loop
vertex 151.16339 -101.600006 -76.2
vertex 78.613464 -0 -25.400002
vertex 151.16339 -0 -76.2
vertex 5.9513144 -4 -3
vertex 3.0950184 -0 -1
vertex 5.9513144 -0 -3
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 151.16339 -101.600006 -76.2
vertex 151.16339 -0 -76.2
vertex 241.3 -101.600006 -76.2
vertex 5.9513144 -4 -3
vertex 5.9513144 -0 -3
vertex 9.5 -4 -3
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 241.3 -101.600006 -76.2
vertex 151.16339 -0 -76.2
vertex 241.3 -0 -76.2
vertex 9.5 -4 -3
vertex 5.9513144 -0 -3
vertex 9.5 -0 -3
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 241.3 -101.600006 -76.2
vertex 241.3 -0 -76.2
vertex 241.3 -101.600006 -63.5
vertex 9.5 -4 -3
vertex 9.5 -0 -3
vertex 9.5 -4 -2.5
endloop
endfacet
facet normal 1 -0 0
outer loop
vertex 241.3 -101.600006 -63.5
vertex 241.3 -0 -76.2
vertex 241.3 -0 -63.5
endloop
endfacet
facet normal 0 -0 1
outer loop
vertex 241.3 -101.600006 -63.5
vertex 241.3 -0 -63.5
vertex 155.16768 -101.600006 -63.5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 155.16768 -101.600006 -63.5
vertex 241.3 -0 -63.5
vertex 155.16768 -0 -63.5
endloop
endfacet
facet normal 0.5735765 0 0.81915194
outer loop
vertex 87.15214 -101.600006 -15.875
vertex 109.82398 -101.600006 -31.75
vertex 109.82398 -0 -31.75
endloop
endfacet
facet normal 0.57357645 0 0.819152
outer loop
vertex 109.82398 -101.600006 -31.75
vertex 155.16768 -101.600006 -63.5
vertex 155.16768 -0 -63.5
endloop
endfacet
facet normal 0.57357645 0 0.81915206
outer loop
vertex 87.15214 -0 -15.875
vertex 64.480286 -101.600006 0
vertex 87.15214 -101.600006 -15.875
endloop
endfacet
facet normal 0.5735765 0 0.81915194
outer loop
vertex 109.82398 -0 -31.75
vertex 87.15214 -0 -15.875
vertex 87.15214 -101.600006 -15.875
endloop
endfacet
facet normal 0.57357645 -0 0.819152
outer loop
vertex 109.82398 -101.600006 -31.75
vertex 155.16768 -0 -63.5
vertex 109.82398 -0 -31.75
endloop
endfacet
facet normal 0.57357645 -0 0.81915206
outer loop
vertex 64.480286 -101.600006 0
vertex 87.15214 -0 -15.875
vertex 64.480286 -0 0
endloop
endfacet
facet normal 0.4226182 0 -0.9063078
outer loop
vertex 84.906715 -101.600006 9.525
vertex 64.480286 -101.600006 0
vertex 64.480286 -0 0
endloop
endfacet
facet normal 0.42261833 0 -0.90630776
outer loop
vertex 105.33314 -101.600006 19.05
vertex 84.906715 -101.600006 9.525
vertex 84.906715 -0 9.525
endloop
endfacet
facet normal 0.4226182 0 -0.9063078
outer loop
vertex 84.906715 -0 9.525
vertex 84.906715 -101.600006 9.525
vertex 64.480286 -0 0
endloop
endfacet
facet normal 0.4226183 0 -0.9063078
outer loop
vertex 105.33314 -0 19.05
vertex 146.18599 -101.600006 38.1
vertex 105.33314 -101.600006 19.05
endloop
endfacet
facet normal 0.42261833 0 -0.90630776
outer loop
vertex 105.33314 -101.600006 19.05
vertex 84.906715 -0 9.525
vertex 105.33314 -0 19.05
endloop
endfacet
facet normal 0.4226183 0 -0.9063078
outer loop
vertex 146.18599 -101.600006 38.1
vertex 105.33314 -0 19.05
vertex 146.18599 -0 38.1
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 146.18599 -101.600006 38.1
vertex 146.18599 -0 38.1
vertex 241.3 -101.600006 38.1
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 241.3 -101.600006 38.1
vertex 146.18599 -0 38.1
vertex 241.3 -0 38.1
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 241.3 -101.600006 38.1
vertex 241.3 -0 38.1
vertex 241.3 -101.600006 50.800003
endloop
endfacet
facet normal 1 -0 0
outer loop
vertex 241.3 -101.600006 50.800003
vertex 241.3 -0 38.1
vertex 241.3 -0 50.800003
vertex 9.5 -4 -2.5
vertex 9.5 -0 -3
vertex 9.5 -0 -2.5
endloop
endfacet
facet normal 0 -0 0.99999994
outer loop
vertex 241.3 -101.600006 50.800003
vertex 241.3 -0 50.800003
vertex 143.37048 -101.600006 50.800003
vertex 9.5 -4 -2.5
vertex 9.5 -0 -2.5
vertex 6.108964 -4 -2.5
endloop
endfacet
facet normal 0 0 0.99999994
outer loop
vertex 143.37048 -101.600006 50.800003
vertex 241.3 -0 50.800003
vertex 143.37048 -0 50.800003
vertex 6.108964 -4 -2.5
vertex 9.5 -0 -2.5
vertex 6.108964 -0 -2.5
endloop
endfacet
facet normal -0.42261827 0 0.9063078
facet normal 0.5735763 0 0.8191522
outer loop
vertex 143.37048 -101.600006 50.800003
vertex 143.37048 -0 50.800003
vertex 88.9 -101.600006 25.400002
vertex 3.4311862 -4 -0.625
vertex 4.323779 -4 -1.25
vertex 4.323779 -0 -1.25
endloop
endfacet
facet normal -0.42261827 0 0.9063078
facet normal 0.57357645 0 0.819152
outer loop
vertex 88.9 -101.600006 25.400002
vertex 143.37048 -0 50.800003
vertex 88.9 -0 25.400002
vertex 4.323779 -4 -1.25
vertex 6.108964 -4 -2.5
vertex 6.108964 -0 -2.5
endloop
endfacet
facet normal 0.57357645 0 0.819152
outer loop
vertex 3.4311862 -0 -0.625
vertex 2.5385938 -4 0
vertex 3.4311862 -4 -0.625
endloop
endfacet
facet normal 0.5735763 0 0.8191522
outer loop
vertex 4.323779 -0 -1.25
vertex 3.4311862 -0 -0.625
vertex 3.4311862 -4 -0.625
endloop
endfacet
facet normal 0.57357645 -0 0.819152
outer loop
vertex 4.323779 -4 -1.25
vertex 6.108964 -0 -2.5
vertex 4.323779 -0 -1.25
endloop
endfacet
facet normal 0.57357645 -0 0.819152
outer loop
vertex 2.5385938 -4 0
vertex 3.4311862 -0 -0.625
vertex 2.5385938 -0 0
endloop
endfacet
facet normal 0.42261824 0 -0.9063078
outer loop
vertex 3.342784 -4 0.375
vertex 2.5385938 -4 0
vertex 2.5385938 -0 0
endloop
endfacet
facet normal 0.42261824 0 -0.9063078
outer loop
vertex 4.146974 -4 0.75
vertex 3.342784 -4 0.375
vertex 3.342784 -0 0.375
endloop
endfacet
facet normal 0.42261824 0 -0.9063078
outer loop
vertex 3.342784 -0 0.375
vertex 3.342784 -4 0.375
vertex 2.5385938 -0 0
endloop
endfacet
facet normal 0.42261833 0 -0.90630776
outer loop
vertex 4.146974 -0 0.75
vertex 5.755354 -4 1.5
vertex 4.146974 -4 0.75
endloop
endfacet
facet normal 0.42261824 0 -0.9063078
outer loop
vertex 4.146974 -4 0.75
vertex 3.342784 -0 0.375
vertex 4.146974 -0 0.75
endloop
endfacet
facet normal 0.42261833 0 -0.90630776
outer loop
vertex 5.755354 -4 1.5
vertex 4.146974 -0 0.75
vertex 5.755354 -0 1.5
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 5.755354 -4 1.5
vertex 5.755354 -0 1.5
vertex 9.5 -4 1.5
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 9.5 -4 1.5
vertex 5.755354 -0 1.5
vertex 9.5 -0 1.5
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 9.5 -4 1.5
vertex 9.5 -0 1.5
vertex 9.5 -4 2
endloop
endfacet
facet normal 1 -0 0
outer loop
vertex 9.5 -4 2
vertex 9.5 -0 1.5
vertex 9.5 -0 2
endloop
endfacet
facet normal 0 -0 1
outer loop
vertex 88.9 -101.600006 25.400002
vertex 88.9 -0 25.400002
vertex 0 -101.600006 25.400002
vertex 9.5 -4 2
vertex 9.5 -0 2
vertex 5.644507 -4 2
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 -101.600006 25.400002
vertex 88.9 -0 25.400002
vertex 0 -0 25.400002
vertex 5.644507 -4 2
vertex 9.5 -0 2
vertex 5.644507 -0 2
endloop
endfacet
facet normal -0.42261824 0 0.90630776
outer loop
vertex 5.644507 -4 2
vertex 5.644507 -0 2
vertex 3.5 -4 1
endloop
endfacet
facet normal -0.42261824 0 0.90630776
outer loop
vertex 3.5 -4 1
vertex 5.644507 -0 2
vertex 3.5 -0 1
endloop
endfacet
facet normal 0 -0 1
outer loop
vertex 3.5 -4 1
vertex 3.5 -0 1
vertex 0 -4 1
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 -4 1
vertex 3.5 -0 1
vertex 0 -0 1
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 0 -101.600006 25.400002
vertex 0 -0 25.400002
vertex 0 -101.600006 0
vertex 0 -4 1
vertex 0 -0 1
vertex 0 -4 0
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 0 -101.600006 0
vertex 0 -0 25.400002
vertex 0 -4 0
vertex 0 -0 1
vertex 0 -0 0
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 84.906715 -0 9.525
vertex 64.480286 -0 0
vertex 88.9 -0 25.400002
vertex 3.342784 -0 0.375
vertex 2.5385938 -0 0
vertex 3.5 -0 1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 105.33314 -0 19.05
vertex 84.906715 -0 9.525
vertex 88.9 -0 25.400002
vertex 4.146974 -0 0.75
vertex 3.342784 -0 0.375
vertex 3.5 -0 1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 87.15214 -0 -15.875
vertex 109.82398 -0 -31.75
vertex 78.613464 -0 -25.400002
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 105.33314 -0 19.05
vertex 143.37048 -0 50.800003
vertex 146.18599 -0 38.1
endloop
endfacet
facet normal -0 1 0
outer loop
vertex 0 -0 25.400002
vertex 88.9 -0 25.400002
vertex 64.480286 -0 0
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 0 -0 25.400002
vertex 64.480286 -0 0
vertex 0 -0 0
endloop
endfacet
facet normal -0 1 0
outer loop
vertex 143.37048 -0 50.800003
vertex 241.3 -0 50.800003
vertex 146.18599 -0 38.1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 241.3 -0 50.800003
vertex 241.3 -0 38.1
vertex 146.18599 -0 38.1
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 105.33314 -0 19.05
vertex 88.9 -0 25.400002
vertex 143.37048 -0 50.800003
vertex 3.4311862 -0 -0.625
vertex 4.323779 -0 -1.25
vertex 3.0950184 -0 -1
endloop
endfacet
facet normal 0 0.99999994 0
outer loop
vertex 64.480286 -0 0
vertex 87.15214 -0 -15.875
vertex 78.613464 -0 -25.400002
vertex 4.146974 -0 0.75
vertex 5.644507 -0 2
vertex 5.755354 -0 1.5
endloop
endfacet
facet normal -0 1 0
outer loop
vertex 0 -0 1
vertex 3.5 -0 1
vertex 2.5385938 -0 0
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 109.82398 -0 -31.75
vertex 151.16339 -0 -76.2
vertex 78.613464 -0 -25.400002
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 155.16768 -0 -63.5
vertex 151.16339 -0 -76.2
vertex 109.82398 -0 -31.75
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 241.3 -0 -63.5
vertex 241.3 -0 -76.2
vertex 155.16768 -0 -63.5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 155.16768 -0 -63.5
vertex 241.3 -0 -76.2
vertex 151.16339 -0 -76.2
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 64.480286 -0 0
vertex 78.613464 -0 -25.400002
vertex 0 -0 -25.400002
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 0 -0 -25.400002
vertex 0 -0 1
vertex 2.5385938 -0 0
vertex 0 -0 0
vertex 64.480286 -0 0
endloop
endfacet
facet normal -0 1 0
outer loop
vertex 5.644507 -0 2
vertex 9.5 -0 2
vertex 5.755354 -0 1.5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 9.5 -0 2
vertex 9.5 -0 1.5
vertex 5.755354 -0 1.5
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 4.146974 -0 0.75
vertex 3.5 -0 1
vertex 5.644507 -0 2
endloop
endfacet
facet normal 0 0.99999994 0
outer loop
vertex 2.5385938 -0 0
vertex 3.4311862 -0 -0.625
vertex 3.0950184 -0 -1
endloop
endfacet
facet normal 0 0.99999994 0
outer loop
vertex 4.323779 -0 -1.25
vertex 5.9513144 -0 -3
vertex 3.0950184 -0 -1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 6.108964 -0 -2.5
vertex 5.9513144 -0 -3
vertex 4.323779 -0 -1.25
endloop
endfacet
facet normal 0 0.99999994 0
outer loop
vertex 9.5 -0 -2.5
vertex 9.5 -0 -3
vertex 6.108964 -0 -2.5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 6.108964 -0 -2.5
vertex 9.5 -0 -3
vertex 5.9513144 -0 -3
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 2.5385938 -0 0
vertex 3.0950184 -0 -1
vertex 0 -0 -1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 0 -0 -1
vertex 0 -0 0
vertex 2.5385938 -0 0
endloop
endfacet
facet normal -0 -1 0
outer loop
vertex 84.906715 -101.600006 9.525
vertex 88.9 -101.600006 25.400002
vertex 64.480286 -101.600006 0
vertex 3.342784 -4 0.375
vertex 3.5 -4 1
vertex 2.5385938 -4 0
endloop
endfacet
facet normal -0 -1 0
outer loop
vertex 105.33314 -101.600006 19.05
vertex 88.9 -101.600006 25.400002
vertex 84.906715 -101.600006 9.525
vertex 4.146974 -4 0.75
vertex 3.5 -4 1
vertex 3.342784 -4 0.375
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 87.15214 -101.600006 -15.875
vertex 78.613464 -101.600006 -25.400002
vertex 109.82398 -101.600006 -31.75
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 105.33314 -101.600006 19.05
vertex 146.18599 -101.600006 38.1
vertex 143.37048 -101.600006 50.800003
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 0 -101.600006 25.400002
vertex 64.480286 -101.600006 0
vertex 88.9 -101.600006 25.400002
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 0 -101.600006 25.400002
vertex 0 -101.600006 0
vertex 64.480286 -101.600006 0
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 143.37048 -101.600006 50.800003
vertex 146.18599 -101.600006 38.1
vertex 241.3 -101.600006 50.800003
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 241.3 -101.600006 50.800003
vertex 146.18599 -101.600006 38.1
vertex 241.3 -101.600006 38.1
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 105.33314 -101.600006 19.05
vertex 143.37048 -101.600006 50.800003
vertex 88.9 -101.600006 25.400002
vertex 3.4311862 -4 -0.625
vertex 3.0950184 -4 -1
vertex 4.323779 -4 -1.25
endloop
endfacet
facet normal 0 -0.99999994 0
outer loop
vertex 64.480286 -101.600006 0
vertex 78.613464 -101.600006 -25.400002
vertex 87.15214 -101.600006 -15.875
endloop
endfacet
facet normal -0 -1 -0
outer loop
vertex 109.82398 -101.600006 -31.75
vertex 78.613464 -101.600006 -25.400002
vertex 151.16339 -101.600006 -76.2
endloop
endfacet
facet normal -0 -1 0
outer loop
vertex 155.16768 -101.600006 -63.5
vertex 109.82398 -101.600006 -31.75
vertex 151.16339 -101.600006 -76.2
endloop
endfacet
facet normal -0 -1 -0
outer loop
vertex 241.3 -101.600006 -63.5
vertex 155.16768 -101.600006 -63.5
vertex 241.3 -101.600006 -76.2
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 155.16768 -101.600006 -63.5
vertex 151.16339 -101.600006 -76.2
vertex 241.3 -101.600006 -76.2
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 64.480286 -101.600006 0
vertex 0 -101.600006 -25.400002
vertex 78.613464 -101.600006 -25.400002
vertex 4.146974 -4 0.75
vertex 5.755354 -4 1.5
vertex 5.644507 -4 2
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 0 -101.600006 -25.400002
vertex 64.480286 -101.600006 0
vertex 0 -101.600006 0
vertex 0 -4 1
vertex 2.5385938 -4 0
vertex 3.5 -4 1
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 0 -4 1
vertex 0 -4 0
vertex 2.5385938 -4 0
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 5.644507 -4 2
vertex 5.755354 -4 1.5
vertex 9.5 -4 2
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 9.5 -4 2
vertex 5.755354 -4 1.5
vertex 9.5 -4 1.5
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 4.146974 -4 0.75
vertex 5.644507 -4 2
vertex 3.5 -4 1
endloop
endfacet
facet normal 0 -0.99999994 0
outer loop
vertex 2.5385938 -4 0
vertex 3.0950184 -4 -1
vertex 3.4311862 -4 -0.625
endloop
endfacet
facet normal -0 -0.99999994 -0
outer loop
vertex 4.323779 -4 -1.25
vertex 3.0950184 -4 -1
vertex 5.9513144 -4 -3
endloop
endfacet
facet normal -0 -1 0
outer loop
vertex 6.108964 -4 -2.5
vertex 4.323779 -4 -1.25
vertex 5.9513144 -4 -3
endloop
endfacet
facet normal -0 -0.99999994 -0
outer loop
vertex 9.5 -4 -2.5
vertex 6.108964 -4 -2.5
vertex 9.5 -4 -3
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 6.108964 -4 -2.5
vertex 5.9513144 -4 -3
vertex 9.5 -4 -3
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 2.5385938 -4 0
vertex 0 -4 -1
vertex 3.0950184 -4 -1
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 0 -4 -1
vertex 2.5385938 -4 0
vertex 0 -4 0
endloop
endfacet
endsolid unnamed

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -3,8 +3,6 @@ import { secrets } from './secrets'
import { getUtils } from './test-utils'
import waitOn from 'wait-on'
import { Themes } from '../../src/lib/theme'
import { roundOff } from 'lib/utils'
import { platform } from 'node:os'
/*
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
@ -16,12 +14,6 @@ document.addEventListener('mousemove', (e) =>
)
*/
const commonPoints = {
startAt: '[9.06, -12.22]',
num1: 9.14,
num2: 18.2,
}
test.beforeEach(async ({ context, page }) => {
// wait for Vite preview server to be up
await waitOn({
@ -60,15 +52,14 @@ test('Basic sketch', async ({ page }) => {
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
// select a plane
await page.mouse.click(700, 200)
@ -81,33 +72,35 @@ test('Basic sketch', async ({ page }) => {
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt = '[23.89, -32.23]'
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
|> startProfileAt(${startAt}, %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num = 24.11
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)`)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num + 0.01}], %)`)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)
|> line([-${commonPoints.num2}, 0], %)`)
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num + 0.01}], %)
|> line([-48, 0], %)`)
// deselect line tool
await page.getByRole('button', { name: 'Line' }).click()
@ -129,132 +122,12 @@ test('Basic sketch', async ({ page }) => {
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line({ to: [${commonPoints.num1}, 0], tag: 'seg01' }, %)
|> line([0, ${commonPoints.num1}], %)
|> startProfileAt(${startAt}, %)
|> line({ to: [${num}, 0], tag: 'seg01' }, %)
|> line([0, ${num + 0.01}], %)
|> angledLine([180, segLen('seg01', %)], %)`)
})
test('Can moving camera', async ({ page, context }) => {
test.skip(process.platform === 'darwin', 'Can moving camera')
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openAndClearDebugPanel()
const camPos: [number, number, number] = [0, 85, 85]
const bakeInRetries = async (
mouseActions: any,
xyz: [number, number, number],
cnt = 0
) => {
// hack that we're implemented our own retry instead of using retries built into playwright.
// however each of these camera drags can be flaky, because of udp
// and so putting them together means only one needs to fail to make this test extra flaky.
// this way we can retry within the test
// We could break them out into separate tests, but the longest past of the test is waiting
// for the stream to start, so it can be good to bundle related things together.
await u.updateCamPosition(camPos)
await page.waitForTimeout(100)
// rotate
await u.closeDebugPanel()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
// const yo = page.getByTestId('cam-x-position').inputValue()
await u.doAndWaitForImageDiff(async () => {
await mouseActions()
await u.openAndClearDebugPanel()
await u.closeDebugPanel()
await page.waitForTimeout(100)
}, 300)
await u.openAndClearDebugPanel()
const vals = await Promise.all([
page.getByTestId('cam-x-position').inputValue(),
page.getByTestId('cam-y-position').inputValue(),
page.getByTestId('cam-z-position').inputValue(),
])
const xError = Math.abs(Number(vals[0]) + xyz[0])
const yError = Math.abs(Number(vals[1]) + xyz[1])
const zError = Math.abs(Number(vals[2]) + xyz[2])
let shouldRetry = false
if (xError > 5 || yError > 5 || zError > 5) {
if (cnt > 2) {
console.log('xVal', vals[0], 'xError', xError)
console.log('yVal', vals[1], 'yError', yError)
console.log('zVal', vals[2], 'zError', zError)
throw new Error('Camera position not as expected')
}
shouldRetry = true
}
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(100)
if (shouldRetry) await bakeInRetries(mouseActions, xyz, cnt + 1)
}
await bakeInRetries(async () => {
await page.mouse.move(700, 200)
await page.mouse.down({ button: 'right' })
await page.mouse.move(600, 303)
await page.mouse.up({ button: 'right' })
}, [4, -10.5, -120])
await bakeInRetries(async () => {
await page.keyboard.down('Shift')
await page.mouse.move(600, 200)
await page.mouse.down({ button: 'right' })
await page.mouse.move(700, 200, { steps: 2 })
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Shift')
}, [-10, -85, -85])
await u.updateCamPosition(camPos)
await u.clearCommandLogs()
await u.closeDebugPanel()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(200)
// zoom
await u.doAndWaitForImageDiff(async () => {
await page.keyboard.down('Control')
await page.mouse.move(700, 400)
await page.mouse.down({ button: 'right' })
await page.mouse.move(700, 300)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Control')
await u.openDebugPanel()
await page.waitForTimeout(300)
await u.clearCommandLogs()
await u.closeDebugPanel()
}, 300)
// zoom with scroll
await u.openAndClearDebugPanel()
// TODO, it appears we don't get the cam setting back from the engine when the interaction is zoom into `backInRetries` once the information is sent back on zoom
// await expect(Math.abs(Number(await page.getByTestId('cam-x-position').inputValue()) + 12)).toBeLessThan(1.5)
// await expect(Math.abs(Number(await page.getByTestId('cam-y-position').inputValue()) - 85)).toBeLessThan(1.5)
// await expect(Math.abs(Number(await page.getByTestId('cam-z-position').inputValue()) - 85)).toBeLessThan(1.5)
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await bakeInRetries(async () => {
await page.mouse.move(700, 400)
await page.mouse.wheel(0, -100)
}, [1, -94, -94])
})
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1000, height: 500 })
@ -403,9 +276,10 @@ test('Can create sketches on all planes and their back sides', async ({
}) => {
await u.openDebugPanel()
await u.updateCamPosition(viewCmd)
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.updateCamPosition(viewCmd)
await u.closeDebugPanel()
await page.mouse.click(clickCoords.x, clickCoords.y)
@ -431,9 +305,11 @@ test('Can create sketches on all planes and their back sides', async ({
}
const codeTemplate = (
plane = 'XY'
plane = 'XY',
rounded = false,
otherThing = '1'
) => `const part001 = startSketchOn('${plane}')
|> startProfileAt([1.14, -1.54], %)`
|> startProfileAt([28.9${otherThing}, -39${rounded ? '' : '.01'}], %)`
await TestSinglePlane({
viewCmd: camPos,
expectedCode: codeTemplate('XY'),
@ -442,8 +318,8 @@ test('Can create sketches on all planes and their back sides', async ({
})
await TestSinglePlane({
viewCmd: camPos,
expectedCode: codeTemplate('YZ'),
clickCoords: { x: 700, y: 250 }, // green plane
expectedCode: codeTemplate('YZ', true),
clickCoords: { x: 700, y: 300 }, // green plane
})
await TestSinglePlane({
viewCmd: camPos,
@ -453,7 +329,7 @@ test('Can create sketches on all planes and their back sides', async ({
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
await TestSinglePlane({
viewCmd: camCmdBackSide,
expectedCode: codeTemplate('-XY'),
expectedCode: codeTemplate('-XY', false, '3'),
clickCoords: { x: 601, y: 118 }, // back of red plane
})
await TestSinglePlane({
@ -463,7 +339,7 @@ test('Can create sketches on all planes and their back sides', async ({
})
await TestSinglePlane({
viewCmd: camCmdBackSide,
expectedCode: codeTemplate('-XZ'),
expectedCode: codeTemplate('-XZ', true),
clickCoords: { x: 680, y: 427 }, // back of blue plane
})
})
@ -499,21 +375,16 @@ test('Auto complete works', async ({ page }) => {
await page.keyboard.type(' |> lin')
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible()
await page.waitForTimeout(100)
// press arrow down twice then enter to accept xLine
await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter')
// finish line with comment
await page.keyboard.type('(5, %) // lin')
await page.waitForTimeout(100)
// there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await page.keyboard.type('(5, %)')
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> xLine(5, %) // lin`)
|> xLine(5, %)`)
})
// Onboarding tests
@ -581,9 +452,6 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
await u.clearCommandLogs()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click()
// select a plane
@ -592,32 +460,35 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt = '[23.89, -32.23]'
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
|> startProfileAt(${startAt}, %)`)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
const num = 24.11
const num2 = '48'
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)`)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num + 0.01}], %)`)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)
|> line([-${commonPoints.num2}, 0], %)`)
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num + 0.01}], %)
|> line([-${num2}, 0], %)`)
// deselect line tool
await page.getByRole('button', { name: 'Line' }).click()
@ -667,7 +538,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await emptySpaceClick()
// check the same selection again by putting cursor in code first then selecting axis
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.getByText(` |> line([-${num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await expect(absYButton).toBeDisabled()
await xAxisClick()
@ -678,7 +549,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await emptySpaceClick()
// select segment in editor than another segment in scene and check there are two cursors
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.getByText(` |> line([-${num2}, 0], %)`).click()
await page.waitForTimeout(300)
await page.keyboard.down('Shift')
await expect(page.locator('.cm-cursor')).toHaveCount(1)
@ -702,13 +573,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await u.closeDebugPanel()
// select a line
// await topHorzSegmentClick()
await page.getByText(commonPoints.startAt).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.waitForTimeout(100)
await topHorzSegmentClick()
await page.waitForTimeout(200)
// enter sketch again
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(300) // wait for animation
await page.waitForTimeout(700) // wait for animation
// hover again and check it works
await selectionSequence()
@ -743,12 +613,12 @@ test('Command bar works and can change a setting', async ({ page }) => {
const themeOption = page.getByRole('option', { name: 'Set Theme' })
await expect(themeOption).toBeVisible()
await themeOption.click()
const themeInput = page.getByPlaceholder('system')
const themeInput = page.getByPlaceholder('Select an option')
await expect(themeInput).toBeVisible()
await expect(themeInput).toBeFocused()
// Select dark theme
await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowDown')
await expect(page.getByRole('option', { name: Themes.Dark })).toHaveAttribute(
'data-headlessui-state',
'active'
@ -765,15 +635,12 @@ test('Can extrude from the command bar', async ({ page, context }) => {
await context.addInitScript(async (token) => {
localStorage.setItem(
'persistCode',
`
const distance = sqrt(20)
const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -14.93], %)
|> line([-23.44, 0.52], %)
|> close(%)
`
`const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -14.93], %)
|> line([-23.44, 0.52], %)
|> close(%)`
)
})
@ -798,44 +665,24 @@ test('Can extrude from the command bar', async ({ page, context }) => {
// Click to select face and set distance
await page.getByText('|> startProfileAt([-6.95, 4.98], %)').click()
await page.getByRole('button', { name: 'Continue' }).click()
// Assert that we're on the distance step
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
// Assert that the an alternative variable name is chosen,
// since the default variable name is already in use (distance)
await page.getByRole('button', { name: 'Create new variable' }).click()
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
'distance001'
)
await expect(page.getByRole('button', { name: 'Continue' })).toBeEnabled()
await page.getByRole('button', { name: 'Continue' }).click()
// Review step and argument hotkeys
await expect(
page.getByRole('button', { name: 'Submit command' })
).toBeEnabled()
await page.keyboard.press('Backspace')
await expect(
page.getByRole('button', { name: 'Distance 12', exact: false })
).toBeDisabled()
await page.keyboard.press('Enter')
await expect(page.getByText('Confirm Extrude')).toBeVisible()
// Review step and argument hotkeys
await page.keyboard.press('2')
await expect(page.getByRole('button', { name: '5' })).toBeDisabled()
await page.keyboard.press('Enter')
// Check that the code was updated
await page.keyboard.press('Enter')
// Unfortunately this indentation seems to matter for the test
await expect(page.locator('.cm-content')).toHaveText(
`const distance = sqrt(20)
const distance001 = 5 + 7
const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -14.93], %)
|> line([-23.44, 0.52], %)
|> close(%)
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
`const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -14.93], %)
|> line([-23.44, 0.52], %)
|> close(%)
|> extrude(5, %)`
)
})
@ -847,9 +694,6 @@ test('Can add multiple sketches', async ({ page }) => {
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
@ -870,32 +714,34 @@ test('Can add multiple sketches', async ({ page }) => {
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt = '[23.89, -32.23]'
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
|> startProfileAt(${startAt}, %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num = 24.11
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)`)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num + 0.01}], %)`)
await page.mouse.click(startXPx, 500 - PUR * 20)
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)
|> line([-${commonPoints.num2}, 0], %)`
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num + 0.01}], %)
|> line([-48, 0], %)`
await expect(page.locator('.cm-content')).toHaveText(finalCodeFirstSketch)
// exit the sketch
@ -917,7 +763,7 @@ test('Can add multiple sketches', async ({ page }) => {
await u.clearAndCloseDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt2 = '[0.93,-1.25]'
const startAt2 = '[23.61, -31.85]'
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
@ -931,7 +777,7 @@ const part002 = startSketchOn('XY')
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num2 = 0.94
const num2 = 23.83
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
@ -949,7 +795,7 @@ const part002 = startSketchOn('XY')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(num2 - 0.01)}], %)`.replace(/\s/g, '')
|> line([0, ${num2}], %)`.replace(/\s/g, '')
)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(
@ -959,8 +805,8 @@ const part002 = startSketchOn('XY')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(num2 - 0.01)}], %)
|> line([-1.87, 0], %)`.replace(/\s/g, '')
|> line([0, ${num2}], %)
|> line([-47.44, 0], %)`.replace(/\s/g, '')
)
})
@ -1003,10 +849,7 @@ test('ProgramMemory can be serialised', async ({ page, context }) => {
})
})
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
page,
context,
}) => {
test('edit selections', async ({ page, context }) => {
const u = getUtils(page)
const selectionsSnippets = {
extrudeAndEditBlocked: '|> startProfileAt([10.81, 32.99], %)',
@ -1054,7 +897,7 @@ fn yohey = (pos) => {
|> line([-15.79, 17.08], %)
return ''
}
yohey([15.79, -34.6])
`
)
@ -1070,11 +913,6 @@ fn yohey = (pos) => {
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// wait for start sketch as a proxy for the stream being ready
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.getByText(selectionsSnippets.extrudeAndEditBlocked).click()
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
await expect(
@ -1110,223 +948,3 @@ fn yohey = (pos) => {
/part005 = startSketchOn\('-XZ'\)/
)
})
test('Deselecting line tool should mean nothing happens on click', async ({
page,
}) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(300)
let previousCodeContent = await page.locator('.cm-content').innerText()
// deselect the line tool by clicking it
await page.getByRole('button', { name: 'Line' }).click()
await page.mouse.click(700, 200)
await page.waitForTimeout(100)
await page.mouse.click(700, 250)
await page.waitForTimeout(100)
await page.mouse.click(750, 200)
await page.waitForTimeout(100)
// expect no change
await expect(page.locator('.cm-content')).toHaveText(previousCodeContent)
// select line tool again
await page.getByRole('button', { name: 'Line' }).click()
await u.closeDebugPanel()
// line tool should work as expected again
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.mouse.click(700, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.mouse.click(750, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
})
test('Can edit segments by dragging their handles', async ({
page,
context,
}) => {
const u = getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
|> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -5.38], %)`
)
})
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
const startPX = [652, 418]
const lineEndPX = [794, 416]
const arcEndPX = [893, 318]
const dragPX = 30
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(100)
let prevContent = await page.locator('.cm-content').innerText()
const step5 = { steps: 5 }
// drag startProfieAt handle
await page.mouse.move(startPX[0], startPX[1])
await page.mouse.down()
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// drag line handle
await page.mouse.move(lineEndPX[0] + dragPX, lineEndPX[1] - dragPX)
await page.mouse.down()
await page.mouse.move(
lineEndPX[0] + dragPX * 2,
lineEndPX[1] - dragPX * 2,
step5
)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// drag tangentialArcTo handle
await page.mouse.move(arcEndPX[0], arcEndPX[1])
await page.mouse.down()
await page.mouse.move(arcEndPX[0] + dragPX, arcEndPX[1] - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([7.01, -11.79], %)
|> line([14.69, 2.73], %)
|> tangentialArcTo([27.6, -3.25], %)`)
})
test('Snap to close works (at any scale)', async ({ page }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
const doSnapAtDifferentScales = async (
camPos: [number, number, number],
expectedCode: string
) => {
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
await u.openAndClearDebugPanel()
await u.updateCamPosition(camPos)
await u.closeDebugPanel()
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')`
)
let prevContent = await page.locator('.cm-content').innerText()
const pointA = [700, 200]
const pointB = [900, 200]
const pointC = [900, 400]
// draw three lines
await page.mouse.click(pointA[0], pointA[1])
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await page.mouse.click(pointB[0], pointB[1])
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await page.mouse.click(pointC[0], pointC[1])
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await page.mouse.move(pointA[0] - 12, pointA[1] + 12)
const pointNotQuiteA = [pointA[0] - 7, pointA[1] + 7]
await page.mouse.move(pointNotQuiteA[0], pointNotQuiteA[1], { steps: 10 })
await page.mouse.click(pointNotQuiteA[0], pointNotQuiteA[1])
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await expect(page.locator('.cm-content')).toHaveText(expectedCode)
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.removeCurrentCode()
}
const codeTemplate = (
scale = 1,
fudge = 0
) => `const part001 = startSketchOn('XZ')
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|> line([${roundOff(scale * 175.36)}, 0], %)
|> line([0, -${roundOff(scale * 175.37) + fudge}], %)
|> close(%)`
await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01))
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
})

View File

@ -29,10 +29,92 @@ test.beforeEach(async ({ context, page }) => {
await page.emulateMedia({ reducedMotion: 'reduce' })
})
test.setTimeout(60_000)
test.setTimeout(60000)
test('change camera, show planes', async ({ page, context }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openAndClearDebugPanel()
const camPos: [number, number, number] = [0, 85, 85]
await u.updateCamPosition(camPos)
// rotate
await u.closeDebugPanel()
await page.mouse.move(700, 200)
await page.mouse.down({ button: 'right' })
await page.mouse.move(600, 300)
await page.mouse.up({ button: 'right' })
await u.openDebugPanel()
await page.waitForTimeout(500)
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.closeDebugPanel()
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.updateCamPosition(camPos)
await u.clearCommandLogs()
await u.closeDebugPanel()
// pan
await page.keyboard.down('Shift')
await page.mouse.move(600, 200)
await page.mouse.down({ button: 'right' })
await page.mouse.move(700, 200)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Shift')
await u.openDebugPanel()
await page.waitForTimeout(300)
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.closeDebugPanel()
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.updateCamPosition(camPos)
await u.clearCommandLogs()
await u.closeDebugPanel()
// zoom
await page.keyboard.down('Control')
await page.mouse.move(700, 400)
await page.mouse.down({ button: 'right' })
await page.mouse.move(700, 300)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Control')
await u.openDebugPanel()
await page.waitForTimeout(300)
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.closeDebugPanel()
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
})
test('exports of each format should work', async ({ page, context }) => {
test.setTimeout(120_000)
// FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed
const u = getUtils(page)
@ -78,7 +160,7 @@ const part001 = startSketchOn('-XZ')
}, %)
|> angledLineToY([segAng('seg02', %) + 180, -baseHeight], %)
|> xLineTo(ZERO, %)
|> close(%)
|> close(%)
|> extrude(4, %)`
)
})
@ -91,6 +173,8 @@ const part001 = startSketchOn('-XZ')
await page.waitForTimeout(1000)
await u.clearAndCloseDebugPanel()
await page.getByRole('button', { name: APP_NAME }).click()
interface Paths {
modelPath: string
imagePath: string
@ -99,21 +183,19 @@ const part001 = startSketchOn('-XZ')
const doExport = async (
output: Models['OutputFormat_type']
): Promise<Paths> => {
await page.getByRole('button', { name: APP_NAME }).click()
await page.getByRole('button', { name: 'Export Part' }).click()
await page.getByRole('button', { name: 'Export Model' }).click()
const exportSelect = page.getByTestId('export-type')
await exportSelect.selectOption({ label: output.type })
// Go through export via command bar
await page.getByRole('option', { name: output.type, exact: false }).click()
if ('storage' in output) {
await page.getByRole('button', { name: 'storage', exact: false }).click()
await page
.getByRole('option', { name: output.storage, exact: false })
.click()
const storageSelect = page.getByTestId('export-storage')
await storageSelect.selectOption({ label: output.storage })
}
await page.getByRole('button', { name: 'Submit command' }).click()
// Handle download
const download = await page.waitForEvent('download')
const downloadPromise = page.waitForEvent('download')
await page.getByRole('button', { name: 'Export', exact: true }).click()
const download = await downloadPromise
const downloadLocationer = (extra = '', isImage = false) =>
`./e2e/playwright/export-snapshots/${output.type}-${
'storage' in output ? output.storage : ''
@ -260,33 +342,24 @@ const part001 = startSketchOn('-XZ')
// snapshot exports, good compromise to capture that exports are healthy without getting bogged down in "did the formatting change" changes
// context: https://github.com/KittyCAD/modeling-app/issues/1222
for (const { modelPath, imagePath, outputType } of exportLocations) {
console.log(
`taking snapshot of using: "zoo file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}"`
)
const cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}`
const cliCommand = `export KITTYCAD_TOKEN=${secrets.snapshottoken} && kittycad file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}`
const child = spawn(cliCommand, { shell: true })
const result = await new Promise<string>((resolve, reject) => {
await new Promise((resolve, reject) => {
child.on('error', (code: any, msg: any) => {
console.log('error', code, msg)
reject('error')
reject()
})
child.on('exit', (code, msg) => {
console.log('exit', code, msg)
if (code !== 0) {
reject(`exit code ${code} for model ${modelPath}`)
} else {
resolve('success')
resolve(true)
}
})
child.stderr.on('data', (data) => console.log(`stderr: ${data}`))
child.stdout.on('data', (data) => console.log(`stdout: ${data}`))
})
expect(result).toBe('success')
if (result === 'success') {
console.log(`snapshot taken for ${modelPath}`)
} else {
console.log(`snapshot failed for ${modelPath}`)
}
}
})
@ -296,13 +369,13 @@ test('extrude on each default plane should be stable', async ({
}) => {
const u = getUtils(page)
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|> startProfileAt([7.00, 4.40], %)
|> line([6.60, -0.20], %)
|> line([2.80, 5.00], %)
|> line([-5.60, 4.40], %)
|> line([-5.40, -3.80], %)
|> startProfileAt([14.06, 8.88], %)
|> line([12.98, -0.15], %)
|> line([5.56, 9.89], %)
|> line([-11.28, 8.96], %)
|> line([-10.81, -7.57], %)
|> close(%)
|> extrude(10.00, %)
|> extrude(20, %)
`
await context.addInitScript(async (code) => {
localStorage.setItem('persistCode', code)
@ -347,23 +420,7 @@ test('extrude on each default plane should be stable', async ({
await runSnapshotsForOtherPlanes('-YZ')
})
test('Draft segments should look right', async ({ page, context }) => {
await context.addInitScript(async () => {
localStorage.setItem(
'SETTINGS_PERSIST_KEY',
JSON.stringify({
baseUnit: 'in',
cameraControls: 'KittyCAD',
defaultDirectory: '',
defaultProjectName: 'project-$nnn',
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
unitSystem: 'imperial',
})
)
})
test('Draft segments should look right', async ({ page }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -371,9 +428,6 @@ test('Draft segments should look right', async ({ page, context }) => {
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
@ -394,9 +448,10 @@ test('Draft segments should look right', async ({ page, context }) => {
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt = '[23.89, -32.23]'
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)`)
|> startProfileAt(${startAt}, %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
@ -408,10 +463,11 @@ test('Draft segments should look right', async ({ page, context }) => {
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num = 24.11
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`)
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
@ -421,202 +477,3 @@ test('Draft segments should look right', async ({ page, context }) => {
maxDiffPixels: 100,
})
})
test('Client side scene scale should match engine scale inch', async ({
page,
context,
}) => {
await context.addInitScript(async () => {
localStorage.setItem(
'SETTINGS_PERSIST_KEY',
JSON.stringify({
baseUnit: 'in',
cameraControls: 'KittyCAD',
defaultDirectory: '',
defaultProjectName: 'project-$nnn',
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
unitSystem: 'imperial',
})
)
})
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)
|> tangentialArcTo([27.34, -3.08], %)`)
// click tangential arc tool again to unequip it
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
})
test('Client side scene scale should match engine scale mm', async ({
page,
context,
}) => {
await context.addInitScript(async () => {
localStorage.setItem(
'SETTINGS_PERSIST_KEY',
JSON.stringify({
baseUnit: 'mm',
cameraControls: 'KittyCAD',
defaultDirectory: '',
defaultProjectName: 'project-$nnn',
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
unitSystem: 'metric',
})
)
})
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([230.03, -310.33], %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([230.03, -310.33], %)
|> line([232.2, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([230.03, -310.33], %)
|> line([232.2, 0], %)
|> tangentialArcTo([694.43, -78.12], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,23 +1,16 @@
import { expect, Page, errors } from '@playwright/test'
import { expect, Page } from '@playwright/test'
import { EngineCommand } from '../../src/lang/std/engineConnection'
import fsp from 'fs/promises'
import pixelMatch from 'pixelmatch'
import { PNG } from 'pngjs'
async function waitForPageLoad(page: Page) {
try {
// wait for 'Loading stream...' spinner
await page.getByTestId('loading-stream').waitFor()
// wait for all spinners to be gone
await page.getByTestId('loading').waitFor({ state: 'detached' })
await page.getByTestId('start-sketch').waitFor()
} catch (e) {
if (e instanceof errors.TimeoutError) {
console.log('Timeout while waiting for page load.')
} else {
throw e // re-throw the error if it is not a TimeoutError
}
}
// wait for 'Loading stream...' spinner
await page.getByTestId('loading-stream').waitFor()
// wait for all spinners to be gone
await page.getByTestId('loading').waitFor({ state: 'detached' })
await page.getByTestId('start-sketch').waitFor()
}
async function removeCurrentCode(page: Page) {

View File

@ -1,69 +0,0 @@
#!/bin/bash
if ! git diff-index --quiet HEAD --; then
echo "Please stash uncommitted changes before running release script"
exit 1
fi
git checkout main
git pull
git fetch --all
# Get the latest semver tag from git
latest_tag=$(jq -r '.version' package.json)
latest_tag="v$latest_tag"
# Print the latest semver tag
echo "Latest semver tag: $latest_tag"
# Function to bump version numbers
bump_version() {
local version=$1
local bump_type=$2
local major=$(echo $version | cut -d '.' -f 1 | sed 's/v//')
local minor=$(echo $version | cut -d '.' -f 2)
local patch=$(echo $version | cut -d '.' -f 3)
case "$bump_type" in
major)
major=$((major + 1))
minor=0
patch=0
;;
minor)
minor=$((minor + 1))
patch=0
;;
*)
patch=$((patch + 1))
;;
esac
echo "v${major}.${minor}.${patch}"
}
# Determine the type of bump based on the argument
bump_type=${1:-patch}
# Bump the version
new_version=$(bump_version $latest_tag $bump_type)
# Print the new semver tag
echo "New semver tag: $new_version"
new_version_number=${new_version:1}
echo "New version number without 'v': $new_version_number"
git checkout -b "cut-release-$new_version"
echo "$(jq --arg v "$new_version_number" '.version=$v' package.json --indent 2)" > package.json
echo "$(jq --arg v "$new_version_number" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json
git add package.json src-tauri/tauri.conf.json
git commit -m "Cut release $new_version"
echo ""
echo "Versions has been bumped in relevant json files, a branch has been created and committed to."
echo ""
echo "What's left for you to do is, push the branch and make the release PR."
echo ""

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.15.4",
"version": "0.15.0",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.10.2",
@ -10,11 +10,12 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.17",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.54",
"@kittycad/lib": "^0.0.53",
"@lezer/javascript": "^1.4.9",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.0",
"@sentry/react": "^7.77.0",
"@tauri-apps/api": "^1.5.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^14.0.0",
@ -26,14 +27,13 @@
"@types/react-dom": "^18.0.0",
"@uiw/react-codemirror": "^4.21.20",
"@xstate/inspect": "^0.8.0",
"@xstate/react": "^3.2.2",
"@xstate/react": "^4.1.0",
"crypto-js": "^4.2.0",
"debounce-promise": "^3.1.2",
"formik": "^2.4.3",
"fuse.js": "^7.0.0",
"http-server": "^14.1.1",
"json-rpc-2.0": "^1.6.0",
"node-fetch": "^3.3.2",
"re-resizable": "^6.9.11",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@ -51,13 +51,13 @@
"ts-node": "^10.9.1",
"typescript": "^5.2.2",
"uuid": "^9.0.1",
"vitest": "^1.3.1",
"vitest": "^0.34.6",
"vscode-jsonrpc": "^8.1.0",
"vscode-languageserver-protocol": "^3.17.5",
"wasm-pack": "^0.12.1",
"web-vitals": "^3.5.0",
"ws": "^8.13.0",
"xstate": "^4.38.2",
"xstate": "^5.7.1",
"zustand": "^4.4.5"
},
"scripts": {
@ -72,6 +72,7 @@
"test": "vitest --mode development",
"test:nowatch": "vitest run --mode development",
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
"test:cov": "vitest run --coverage --mode development",
"test:e2e:tauri": "E2E_TAURI_ENABLED=true TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' wdio run wdio.conf.ts",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
@ -84,7 +85,7 @@
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
"lint": "eslint --fix src",
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
"postinstall": "yarn xstate:typegen",
"postinstall": "patch-package && yarn xstate:typegen",
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\""
},
"prettier": {
@ -112,6 +113,7 @@
"@tauri-apps/cli": "^1.5.6",
"@types/crypto-js": "^4.1.1",
"@types/debounce-promise": "^3.1.8",
"@types/isomorphic-fetch": "^0.0.36",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4",
"@types/react-modal": "^3.16.3",
@ -120,7 +122,8 @@
"@types/wait-on": "^5.3.4",
"@types/wicg-file-system-access": "^2020.9.6",
"@types/ws": "^8.5.5",
"@vitejs/plugin-react": "^4.2.1",
"@vitejs/plugin-react": "^4.1.1",
"@vitest/coverage-istanbul": "^0.34.6",
"@wdio/cli": "^8.24.3",
"@wdio/globals": "^8.24.3",
"@wdio/local-runner": "^8.24.3",
@ -133,6 +136,7 @@
"eslint-plugin-css-modules": "^2.12.0",
"happy-dom": "^10.8.0",
"husky": "^8.0.3",
"patch-package": "^8.0.0",
"pixelmatch": "^5.3.0",
"pngjs": "^7.0.0",
"postcss": "^8.4.31",
@ -140,10 +144,10 @@
"prettier": "^2.8.0",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.3.6",
"vite": "^5.1.3",
"vite": "^4.5.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.1",
"vite-tsconfig-paths": "^4.2.1",
"vitest-webgl-canvas-mock": "^1.1.0",
"wait-on": "^7.2.0",
"yarn": "^1.22.19"

138
patches/three+0.160.0.patch Normal file
View File

@ -0,0 +1,138 @@
diff --git a/node_modules/three/examples/jsm/controls/OrbitControls.js b/node_modules/three/examples/jsm/controls/OrbitControls.js
index f29e7fe..0ef636b 100644
--- a/node_modules/three/examples/jsm/controls/OrbitControls.js
+++ b/node_modules/three/examples/jsm/controls/OrbitControls.js
@@ -113,6 +113,25 @@ class OrbitControls extends EventDispatcher {
// public methods
//
+ this.interactionGuards = {
+ pan: {
+ description: 'Right click + Shift + drag or middle click + drag',
+ callback: (e) => e.button === 2 && !e.ctrlKey,
+ },
+ zoom: {
+ description: 'Scroll wheel or Right click + Ctrl + drag',
+ dragCallback: (e) => e.button === 2 && e.ctrlKey,
+ scrollCallback: () => true,
+ },
+ rotate: {
+ description: 'Right click + drag',
+ callback: (e) => e.button === 0,
+ },
+ }
+ this.setMouseGuards = (interactionGuards) => {
+ this.interactionGuards = interactionGuards
+ }
+
this.getPolarAngle = function () {
return spherical.phi;
@@ -1057,92 +1076,21 @@ class OrbitControls extends EventDispatcher {
function onMouseDown( event ) {
- let mouseAction;
-
- switch ( event.button ) {
-
- case 0:
-
- mouseAction = scope.mouseButtons.LEFT;
- break;
-
- case 1:
-
- mouseAction = scope.mouseButtons.MIDDLE;
- break;
-
- case 2:
-
- mouseAction = scope.mouseButtons.RIGHT;
- break;
-
- default:
-
- mouseAction = - 1;
-
- }
-
- switch ( mouseAction ) {
-
- case MOUSE.DOLLY:
-
- if ( scope.enableZoom === false ) return;
-
- handleMouseDownDolly( event );
-
- state = STATE.DOLLY;
-
- break;
-
- case MOUSE.ROTATE:
-
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
-
- if ( scope.enablePan === false ) return;
-
- handleMouseDownPan( event );
-
- state = STATE.PAN;
-
- } else {
-
- if ( scope.enableRotate === false ) return;
-
- handleMouseDownRotate( event );
-
- state = STATE.ROTATE;
-
- }
-
- break;
-
- case MOUSE.PAN:
-
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
-
- if ( scope.enableRotate === false ) return;
-
- handleMouseDownRotate( event );
-
- state = STATE.ROTATE;
-
- } else {
-
- if ( scope.enablePan === false ) return;
-
- handleMouseDownPan( event );
-
- state = STATE.PAN;
-
- }
-
- break;
-
- default:
-
- state = STATE.NONE;
-
- }
+ if (scope.interactionGuards.pan.callback(event)) {
+ if (scope.enablePan === false) return
+ handleMouseDownPan(event)
+ state = STATE.PAN
+ } else if (scope.interactionGuards.rotate.callback(event)) {
+ if (scope.enableRotate === false) return
+ handleMouseDownRotate(event)
+ state = STATE.ROTATE
+ } else if (scope.interactionGuards.zoom.dragCallback(event)) {
+ if (scope.enableZoom === false) return
+ handleMouseDownDolly(event)
+ state = STATE.DOLLY
+ } else {
+ return
+ }
if ( state !== STATE.NONE ) {

View File

@ -1,34 +1,16 @@
import re
import os
import requests
import os
webhook_url = os.getenv('DISCORD_WEBHOOK_URL')
release_version = os.getenv('RELEASE_VERSION')
release_body = os.getenv('RELEASE_BODY')
# Regular expression to match URLs
url_pattern = r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)'
# Function to encase URLs in <>
def encase_urls_with_angle_brackets(match):
url = match.group(0)
return f'<{url}>'
# Replace all URLs in the release_body with their <> enclosed version
modified_release_body = re.sub(url_pattern, encase_urls_with_angle_brackets, release_body)
# Ensure the modified_release_body does not exceed Discord's character limit
max_length = 500 # Adjust as needed
if len(modified_release_body) > max_length:
modified_release_body = modified_release_body[:max_length].rsplit(' ', 1)[0] # Avoid cutting off in the middle of a word
modified_release_body += "... for full changelog, check out the link above."
# Message to send to Discord
# message to send to Discord
data = {
"content":
f'''
**{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/modeling-app/download>
{modified_release_body}
**{release_version}** is now available! Check out the latest features and improvements here: https://zoo.dev/modeling-app/download
{release_body}
''',
"username": "Modeling App Release Updates",
"avatar_url": "https://raw.githubusercontent.com/KittyCAD/modeling-app/main/public/discord-avatar.png"
@ -41,7 +23,4 @@ response = requests.post(webhook_url, json=data)
if response.status_code == 204:
print("Successfully sent the message to Discord.")
else:
print(f"Failed to send the message to Discord. Status code: {response.status_code}, Response: {response.text}")
print(modified_release_body)
print(data["content"])
print("Failed to send the message to Discord.")

22
src-tauri/Cargo.lock generated
View File

@ -67,9 +67,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.80"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
[[package]]
name = "app"
@ -1664,9 +1664,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.58"
version = "0.2.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049c3881ffbe77bf1c3a968372a246ce906eceb79f61cd0bc5fa229bec3504cb"
checksum = "a086e1a1bbddb3b38959c0f0ce6de6b3a3b7566e38e0b7d5fb101e91911beed4"
dependencies = [
"anyhow",
"async-trait",
@ -3235,9 +3235,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.197"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
@ -3253,9 +3253,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.197"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@ -3275,9 +3275,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.114"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa 1.0.6",
"ryu",
@ -3872,7 +3872,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs-extra"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#ed682dd96eb765e7cd3cdbc3cc64f794a0d6f9df"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#01211ff0759d578e0e9ac8c98c31fdf09077eb34"
dependencies = [
"log",
"serde",

View File

@ -16,7 +16,7 @@ tauri-build = { version = "1.5.1", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.2.58"
kittycad = "0.2.53"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -7,6 +7,7 @@ use std::io::Read;
use anyhow::Result;
use oauth2::TokenResponse;
use std::process::Command;
use tauri::{InvokeError, Manager};
const DEFAULT_HOST: &str = "https://api.kittycad.io";

View File

@ -7,7 +7,7 @@
},
"package": {
"productName": "zoo-modeling-app",
"version": "0.15.4"
"version": "0.15.0"
},
"tauri": {
"allowlist": {

View File

@ -72,7 +72,7 @@ export function App() {
useHotkeys('shift + l', () => togglePane('logs'))
useHotkeys('shift + e', () => togglePane('kclErrors'))
useHotkeys('shift + d', () => togglePane('debug'))
useHotkeys('esc', () => send('Cancel'))
useHotkeys('esc', () => send({ type: 'Cancel' }))
useHotkeys('backspace', (e) => {
e.preventDefault()
})

View File

@ -3,8 +3,15 @@ import {
createBrowserRouter,
Outlet,
redirect,
useLocation,
RouterProvider,
} from 'react-router-dom'
import {
matchRoutes,
createRoutesFromChildren,
useNavigationType,
} from 'react-router'
import { useEffect } from 'react'
import { ErrorPage } from './components/ErrorPage'
import { Settings } from './routes/Settings'
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
@ -28,9 +35,9 @@ import {
settingsMachine,
} from './machines/settingsMachine'
import { ContextFrom } from 'xstate'
import CommandBarProvider, {
CommandBar,
} from 'components/CommandBar/CommandBar'
import CommandBarProvider from 'components/CommandBar/CommandBar'
import { TEST, VITE_KC_SENTRY_DSN } from './env'
import * as Sentry from '@sentry/react'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { KclContextProvider, kclManager } from 'lang/KclSingleton'
import FileMachineProvider from 'components/FileMachineProvider'
@ -39,6 +46,38 @@ import { paths } from 'lib/paths'
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
import { fileSystemManager } from 'lang/std/fileSystemManager'
if (VITE_KC_SENTRY_DSN && !TEST) {
Sentry.init({
dsn: VITE_KC_SENTRY_DSN,
// TODO(paultag): pass in the right env here.
// environment: "production",
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes
),
}),
new Sentry.Replay(),
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
tracesSampleRate: 1.0,
// TODO: Add in kittycad.io endpoints
tracePropagationTargets: ['localhost'],
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
})
}
export const BROWSER_FILE_NAME = 'new'
type CreateBrowserRouterArg = Parameters<typeof createBrowserRouter>[0]
@ -78,7 +117,6 @@ const router = createBrowserRouter(
<ModelingMachineProvider>
<Outlet />
<App />
<CommandBar />
</ModelingMachineProvider>
<WasmErrBanner />
</FileMachineProvider>
@ -178,7 +216,6 @@ const router = createBrowserRouter(
<Auth>
<Outlet />
<Home />
<CommandBar />
</Auth>
),
loader: async (): Promise<HomeLoaderData | Response> => {

View File

@ -6,12 +6,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { ActionButton } from 'components/ActionButton'
import usePlatform from 'hooks/usePlatform'
import { isSingleCursorInPipe } from 'lang/queryAst'
import { kclManager, useKclContext } from 'lang/KclSingleton'
import {
NetworkHealthState,
useNetworkStatus,
} from 'components/NetworkHealthIndicator'
import { useStore } from 'useStore'
import { kclManager } from 'lang/KclSingleton'
export const Toolbar = () => {
const platform = usePlatform()
@ -29,13 +24,6 @@ export const Toolbar = () => {
context.selectionRanges
)
}, [engineCommandManager.artifactMap, context.selectionRanges])
const { overallState } = useNetworkStatus()
const { isExecuting } = useKclContext()
const { isStreamReady } = useStore((s) => ({
isStreamReady: s.isStreamReady,
}))
const disableAllButtons =
overallState !== NetworkHealthState.Ok || isExecuting || !isStreamReady
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
const span = toolbarButtonsRef.current
@ -72,7 +60,6 @@ export const Toolbar = () => {
icon: 'sketch',
bgClassName,
}}
disabled={disableAllButtons}
>
<span data-testid="start-sketch">Start Sketch</span>
</ActionButton>
@ -87,7 +74,6 @@ export const Toolbar = () => {
icon: 'sketch',
bgClassName,
}}
disabled={disableAllButtons}
>
Edit Sketch
</ActionButton>
@ -102,7 +88,6 @@ export const Toolbar = () => {
icon: 'arrowLeft',
bgClassName,
}}
disabled={disableAllButtons}
>
Exit Sketch
</ActionButton>
@ -115,8 +100,8 @@ export const Toolbar = () => {
Element="button"
onClick={() =>
state?.matches('Sketch.Line tool')
? send('CancelSketch')
: send('Equip Line tool')
? send({ type: 'CancelSketch' })
: send({ type: 'Equip Line tool' })
}
aria-pressed={state?.matches('Sketch.Line tool')}
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
@ -124,7 +109,6 @@ export const Toolbar = () => {
icon: 'line',
bgClassName,
}}
disabled={disableAllButtons}
>
Line
</ActionButton>
@ -134,19 +118,18 @@ export const Toolbar = () => {
Element="button"
onClick={() =>
state.matches('Sketch.Tangential arc to')
? send('CancelSketch')
: send('Equip tangential arc to')
? send({ type: 'CancelSketch' })
: send({ type: 'Equip tangential arc to' })
}
aria-pressed={state.matches('Sketch.Tangential arc to')}
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
icon={{
icon: 'arc',
icon: 'line',
bgClassName,
}}
disabled={
(!state.can('Equip tangential arc to') &&
!state.matches('Sketch.Tangential arc to')) ||
disableAllButtons
!state.can({ type: 'Equip tangential arc to' }) &&
!state.matches('Sketch.Tangential arc to')
}
>
Tangential Arc
@ -186,7 +169,7 @@ export const Toolbar = () => {
disabled={
!state.nextEvents
.filter((event) => state.can(event as any))
.includes(eventName) || disableAllButtons
.includes(eventName)
}
title={eventName}
icon={{
@ -211,9 +194,9 @@ export const Toolbar = () => {
data: { name: 'Extrude', ownerMachine: 'modeling' },
})
}
disabled={!state.can('Extrude') || disableAllButtons}
disabled={!state.can({ type: 'Extrude' })}
title={
state.can('Extrude')
state.can({ type: 'Extrude' })
? 'extrude'
: 'sketches need to be closed, or not already extruded'
}

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,11 @@ import { useModelingContext } from 'hooks/useModelingContext'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useStore } from 'useStore'
import { DEBUG_SHOW_BOTH_SCENES, sceneInfra } from './sceneInfra'
import { ReactCameraProperties } from './CameraControls'
import {
DEBUG_SHOW_BOTH_SCENES,
ReactCameraProperties,
sceneInfra,
} from './sceneInfra'
import { throttle } from 'lib/utils'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
@ -15,7 +18,7 @@ function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const { state } = useModelingContext()
useEffect(() => {
sceneInfra.camControls.setIsCamMovingCallback((isMoving, isTween) => {
sceneInfra.setIsCamMovingCallback((isMoving, isTween) => {
setIsCamMoving(isMoving)
setIsTween(isTween)
})
@ -49,8 +52,7 @@ export const ClientSideScene = ({
// Listen for changes to the camera controls setting
// and update the client-side scene's controls accordingly.
useEffect(() => {
sceneInfra.camControls.interactionGuards =
cameraMouseDragGuards[cameraControls]
sceneInfra.setInteractionGuards(cameraMouseDragGuards[cameraControls])
}, [cameraControls])
useEffect(() => {
sceneInfra.updateOtherSelectionColors(
@ -91,7 +93,7 @@ export const ClientSideScene = ({
const throttled = throttle((a: ReactCameraProperties) => {
if (a.type === 'perspective' && a.fov) {
sceneInfra.camControls.dollyZoom(a.fov)
sceneInfra.dollyZoom(a.fov)
}
}, 1000 / 15)
@ -105,7 +107,7 @@ export const CamDebugSettings = () => {
const [fov, setFov] = useState(12)
useEffect(() => {
sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings)
sceneInfra.setReactCameraPropertiesCallback(setCamSettings)
}, [sceneInfra])
useEffect(() => {
if (camSettings.type === 'perspective' && camSettings.fov) {
@ -122,9 +124,9 @@ export const CamDebugSettings = () => {
checked={camSettings.type === 'perspective'}
onChange={(e) => {
if (camSettings.type === 'perspective') {
sceneInfra.camControls.useOrthographicCamera()
sceneInfra.useOrthographicCamera()
} else {
sceneInfra.camControls.usePerspectiveCamera()
sceneInfra.usePerspectiveCamera()
}
}}
/>
@ -154,7 +156,7 @@ export const CamDebugSettings = () => {
value={camSettings.fov}
className="text-black w-16"
onChange={(e) => {
sceneInfra.camControls.setCam({
sceneInfra.setCam({
...camSettings,
fov: parseFloat(e.target.value),
})
@ -171,7 +173,7 @@ export const CamDebugSettings = () => {
value={camSettings.zoom}
className="text-black w-16"
onChange={(e) => {
sceneInfra.camControls.setCam({
sceneInfra.setCam({
...camSettings,
zoom: parseFloat(e.target.value),
})
@ -192,7 +194,7 @@ export const CamDebugSettings = () => {
value={camSettings.position[0]}
className="text-black w-16"
onChange={(e) => {
sceneInfra.camControls.setCam({
sceneInfra.setCam({
...camSettings,
position: [
parseFloat(e.target.value),
@ -212,7 +214,7 @@ export const CamDebugSettings = () => {
value={camSettings.position[1]}
className="text-black w-16"
onChange={(e) => {
sceneInfra.camControls.setCam({
sceneInfra.setCam({
...camSettings,
position: [
camSettings.position[0],
@ -232,7 +234,7 @@ export const CamDebugSettings = () => {
value={camSettings.position[2]}
className="text-black w-16"
onChange={(e) => {
sceneInfra.camControls.setCam({
sceneInfra.setCam({
...camSettings,
position: [
camSettings.position[0],

View File

@ -1,4 +1,3 @@
import { compareVec2Epsilon2 } from 'lang/std/sketch'
import {
GridHelper,
LineBasicMaterial,
@ -6,8 +5,6 @@ import {
PerspectiveCamera,
Group,
Mesh,
Quaternion,
Vector3,
} from 'three'
export function createGridHelper({
@ -34,9 +31,3 @@ export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) =>
export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) =>
(group.position.distanceTo(cam.position) * cam.fov) / 4000
export function isQuaternionVertical(q: Quaternion) {
const v = new Vector3(0, 0, 1).applyQuaternion(q)
// no x or y components means it's vertical
return compareVec2Epsilon2([v.x, v.y], [0, 0])
}

View File

@ -3,13 +3,10 @@ import {
DoubleSide,
ExtrudeGeometry,
Group,
Intersection,
LineCurve3,
Matrix4,
Mesh,
MeshBasicMaterial,
Object3D,
Object3DEventMap,
OrthographicCamera,
PerspectiveCamera,
PlaneGeometry,
@ -27,7 +24,7 @@ import {
defaultPlaneColor,
getSceneScale,
INTERSECTION_PLANE_LAYER,
OnMouseEnterLeaveArgs,
isQuaternionVertical,
RAYCASTABLE_PLANE,
sceneInfra,
SKETCH_GROUP_SEGMENTS,
@ -37,7 +34,6 @@ import {
Y_AXIS,
YZ_PLANE,
} from './sceneInfra'
import { isQuaternionVertical } from './helpers'
import {
CallExpression,
getTangentialArcToInfo,
@ -60,7 +56,6 @@ import { engineCommandManager } from 'lang/std/engineConnection'
import {
createArcGeometry,
dashedStraight,
profileStart,
straightSegment,
tangentialArcToSegment,
} from './segments'
@ -68,7 +63,7 @@ import {
addCloseToPipe,
addNewSketchLn,
changeSketchArguments,
updateStartProfileAtArgs,
compareVec2Epsilon2,
} from 'lang/std/sketch'
import { isReducedMotion, throttle } from 'lib/utils'
import {
@ -90,7 +85,6 @@ export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
'tangential-arc-to-segment-body-dashed'
export const PROFILE_START = 'profile-start'
// This singleton Class is responsible for all of the things the user sees and interacts with.
// That mostly mean sketch elements.
@ -104,18 +98,17 @@ class SceneEntities {
currentSketchQuaternion: Quaternion | null = null
constructor() {
this.scene = sceneInfra?.scene
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
sceneInfra?.setOnCamChange(this.onCamChange)
}
onCamChange = () => {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const orthoFactor = orthoScale(sceneInfra.camera)
Object.values(this.activeSegments).forEach((segment) => {
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
sceneInfra.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, segment)) /
sceneInfra._baseUnitMultiplier
: perspScale(sceneInfra.camera, segment)
if (
segment.userData.from &&
segment.userData.to &&
@ -143,29 +136,21 @@ class SceneEntities {
scale: factor,
})
}
if (segment.name === PROFILE_START) {
segment.scale.set(factor, factor, factor)
}
})
if (this.axisGroup) {
const factor =
sceneInfra.camControls.camera instanceof OrthographicCamera
sceneInfra.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
: perspScale(sceneInfra.camera, this.axisGroup)
const x = this.axisGroup.getObjectByName(X_AXIS)
x?.scale.set(1, factor / sceneInfra._baseUnitMultiplier, 1)
x?.scale.set(1, factor, 1)
const y = this.axisGroup.getObjectByName(Y_AXIS)
y?.scale.set(factor / sceneInfra._baseUnitMultiplier, 1, 1)
y?.scale.set(factor, 1, 1)
}
}
createIntersectionPlane() {
if (sceneInfra.scene.getObjectByName(RAYCASTABLE_PLANE)) {
console.warn('createIntersectionPlane called when it already exists')
return
}
const hundredM = 1000000
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
const planeGeometry = new PlaneGeometry(100000, 100000)
const planeMaterial = new MeshBasicMaterial({
color: 0xff0000,
side: DoubleSide,
@ -179,7 +164,6 @@ class SceneEntities {
this.scene.add(this.intersectionPlane)
}
createSketchAxis(sketchPathToNode: PathToNode) {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const baseXColor = 0x000055
const baseYColor = 0x550000
const xAxisGeometry = new BoxGeometry(100000, 0.3, 0.01)
@ -211,22 +195,13 @@ class SceneEntities {
this.axisGroup = new Group()
const gridHelper = createGridHelper({ size: 100, divisions: 10 })
gridHelper.position.z = -0.01
gridHelper.renderOrder = -3 // is this working?
gridHelper.name = 'gridHelper'
const sceneScale = getSceneScale(
sceneInfra.camControls.camera,
sceneInfra.camControls.target
sceneInfra.camera,
sceneInfra.controls.target
)
gridHelper.scale.set(sceneScale, sceneScale, sceneScale)
const factor =
sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
xAxisMesh?.scale.set(1, factor / sceneInfra._baseUnitMultiplier, 1)
yAxisMesh?.scale.set(factor / sceneInfra._baseUnitMultiplier, 1, 1)
this.axisGroup.add(xAxisMesh, yAxisMesh, gridHelper)
this.currentSketchQuaternion &&
this.axisGroup.setRotationFromQuaternion(this.currentSketchQuaternion)
@ -263,8 +238,16 @@ class SceneEntities {
ast?: Program
draftSegment?: DraftSegment
}) {
sceneInfra.resetMouseListeners()
this.createIntersectionPlane()
const distance = sceneInfra.controls.target.distanceTo(
sceneInfra.camera.position
)
// TODO this should probably be distance to the sketch group, more important after sketch on face
// since sketches won't always so close to the origin
// is this the best place to adjust camera far?
if (sceneInfra.camera.far < distance * 1.5) {
sceneInfra.camera.far = distance * 2
}
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
this.prepareTruncatedMemoryAndAst(
@ -296,57 +279,19 @@ class SceneEntities {
sketchGroup.position[1],
sketchGroup.position[2]
)
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const orthoFactor = orthoScale(sceneInfra.camera)
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
sceneInfra.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, dummy)) /
sceneInfra._baseUnitMultiplier
const segPathToNode = getNodePathFromSourceRange(
kclManager.ast,
sketchGroup.start.__geoMeta.sourceRange
)
const _profileStart = profileStart({
from: sketchGroup.start.from,
id: sketchGroup.start.__geoMeta.id,
pathToNode: segPathToNode,
scale: factor,
})
_profileStart.layers.set(SKETCH_LAYER)
_profileStart.traverse((child) => {
child.layers.set(SKETCH_LAYER)
})
group.add(_profileStart)
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
: perspScale(sceneInfra.camera, dummy)
sketchGroup.value.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange(
kclManager.ast,
draftSegment ? truncatedAst : kclManager.ast,
segment.__geoMeta.sourceRange
)
if (draftSegment && (sketchGroup.value[index - 1] || sketchGroup.start)) {
const previousSegment =
sketchGroup.value[index - 1] || sketchGroup.start
const previousSegmentPathToNode = getNodePathFromSourceRange(
kclManager.ast,
previousSegment.__geoMeta.sourceRange
)
const bodyIndex = previousSegmentPathToNode[1][0]
segPathToNode = getNodePathFromSourceRange(
truncatedAst,
segment.__geoMeta.sourceRange
)
segPathToNode[1][0] = bodyIndex
}
const isDraftSegment =
draftSegment && index === sketchGroup.value.length - 1
let seg
const callExpName = getNodeFromPath<CallExpression>(
kclManager.ast,
segPathToNode,
'CallExpression'
)?.node?.callee?.name
if (segment.type === 'TangentialArcTo') {
seg = tangentialArcToSegment({
prevSegment: sketchGroup.value[index - 1],
@ -365,7 +310,6 @@ class SceneEntities {
pathToNode: segPathToNode,
isDraftSegment,
scale: factor,
callExpName,
})
}
seg.layers.set(SKETCH_LAYER)
@ -387,19 +331,17 @@ class SceneEntities {
this.scene.add(group)
if (!draftSegment) {
sceneInfra.setCallbacks({
onDrag: ({ selected, intersectionPoint, mouseEvent, intersects }) => {
if (mouseEvent.which !== 1) return
onDrag: (args) => {
if (args.event.which !== 1) return
this.onDragSegment({
object: selected,
intersection2d: intersectionPoint.twoD,
intersects,
...args,
sketchPathToNode,
})
},
onMove: () => {},
onClick: (args) => {
if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) {
if (args?.event.which !== 1) return
if (!args || !args.object) {
sceneInfra.modelingSend({
type: 'Set selection',
data: {
@ -408,32 +350,73 @@ class SceneEntities {
})
return
}
const { selected } = args
const event = getEventForSegmentSelection(selected)
const { object } = args
const event = getEventForSegmentSelection(object)
if (!event) return
sceneInfra.modelingSend(event)
},
...mouseEnterLeaveCallbacks(),
onMouseEnter: ({ object }) => {
// TODO change the color of the segment to yellow?
// Give a few pixels grace around each of the segments
// for hover.
if ([X_AXIS, Y_AXIS].includes(object?.userData?.type)) {
const obj = object as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
mat.color.offsetHSL(0, 0, 0.5)
}
const parent = getParentGroup(object)
if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast))
const node = getNodeFromPath<CallExpression>(
updatedAst,
parent.userData.pathToNode,
'CallExpression'
).node
sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00
colorSegment(object, yellow)
return
}
sceneInfra.highlightCallback([0, 0])
},
onMouseLeave: ({ object }) => {
sceneInfra.highlightCallback([0, 0])
const parent = getParentGroup(object)
const isSelected = parent?.userData?.isSelected
colorSegment(object, isSelected ? 0x0000ff : 0xffffff)
if ([X_AXIS, Y_AXIS].includes(object?.userData?.type)) {
const obj = object as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
}
},
})
} else {
sceneInfra.setCallbacks({
onDrag: () => {},
onClick: async (args) => {
if (!args) return
if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args
let intersection2d = intersectionPoint?.twoD
const profileStart = args.intersects
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
.find((a) => a?.name === PROFILE_START)
if (args.event.which !== 1) return
const { intersection2d } = args
if (!intersection2d) return
const firstSeg = sketchGroup.value[0]
const isClosingSketch = compareVec2Epsilon2(
firstSeg.from,
[intersection2d.x, intersection2d.y],
1
)
let modifiedAst
if (profileStart) {
if (isClosingSketch) {
// TODO close needs a better UX
modifiedAst = addCloseToPipe({
node: kclManager.ast,
programMemory: kclManager.programMemory,
pathToNode: sketchPathToNode,
})
} else if (intersection2d) {
} else {
const lastSegment = sketchGroup.value.slice(-1)[0]
modifiedAst = addNewSketchLn({
node: kclManager.ast,
@ -446,9 +429,6 @@ class SceneEntities {
: 'line',
pathToNode: sketchPathToNode,
}).modifiedAst
} else {
// return early as we didn't modify the ast
return
}
kclManager.executeAstMock(modifiedAst, { updates: 'code' })
@ -457,9 +437,8 @@ class SceneEntities {
},
onMove: (args) => {
this.onDragSegment({
intersection2d: args.intersectionPoint.twoD,
...args,
object: Object.values(this.activeSegments).slice(-1)[0],
intersects: args.intersects,
sketchPathToNode,
draftInfo: {
draftSegment,
@ -469,10 +448,9 @@ class SceneEntities {
},
})
},
...mouseEnterLeaveCallbacks(),
})
}
sceneInfra.camControls.enableRotate = false
sceneInfra.controls.enableRotate = false
}
updateAstAndRejigSketch = async (
sketchPathToNode: PathToNode,
@ -506,15 +484,17 @@ class SceneEntities {
)
onDragSegment({
object,
intersection2d: _intersection2d,
event,
intersectPoint,
intersection2d,
sketchPathToNode,
draftInfo,
intersects,
}: {
object: any
event: any
intersectPoint: Vector3
intersection2d: Vector2
sketchPathToNode: PathToNode
intersects: Intersection<Object3D<Object3DEventMap>>[]
draftInfo?: {
draftSegment: DraftSegment
truncatedAst: Program
@ -522,20 +502,7 @@ class SceneEntities {
variableDeclarationName: string
}
}) {
const profileStart =
draftInfo &&
intersects
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
.find((a) => a?.name === PROFILE_START)
const intersection2d = profileStart
? new Vector2(profileStart.position.x, profileStart.position.y)
: _intersection2d
const group = getParentGroup(object, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
const group = getParentGroup(object)
if (!group) return
const pathToNode: PathToNode = JSON.parse(
JSON.stringify(group.userData.pathToNode)
@ -559,28 +526,13 @@ class SceneEntities {
).node
if (node.type !== 'CallExpression') return
let modded: {
modifiedAst: Program
pathToNode: PathToNode
}
if (group.name === PROFILE_START) {
modded = updateStartProfileAtArgs({
node: modifiedAst,
pathToNode,
to,
from,
previousProgramMemory: kclManager.programMemory,
})
} else {
modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
to,
from
)
}
const modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
to,
from
)
modifiedAst = modded.modifiedAst
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
draftInfo
@ -599,16 +551,10 @@ class SceneEntities {
programMemoryOverride,
})
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.root[
variableDeclarationName
] as SketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const updateSegment = (
segment: Path | SketchGroup['start'],
index: number
) => {
const sketchGroup = programMemory.root[variableDeclarationName]
.value as Path[]
const orthoFactor = orthoScale(sceneInfra.camera)
sketchGroup.forEach((segment, index) => {
const segPathToNode = getNodePathFromSourceRange(
modifiedAst,
segment.__geoMeta.sourceRange
@ -623,13 +569,12 @@ class SceneEntities {
// const prevSegment = sketchGroup.slice(index - 1)[0]
const type = group?.userData?.type
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
sceneInfra.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, group)) /
sceneInfra._baseUnitMultiplier
: perspScale(sceneInfra.camera, group)
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
this.updateTangentialArcToSegment({
prevSegment: sgPaths[index - 1],
prevSegment: sketchGroup[index - 1],
from: segment.from,
to: segment.to,
group: group,
@ -642,13 +587,8 @@ class SceneEntities {
group: group,
scale: factor,
})
} else if (type === PROFILE_START) {
group.position.set(segment.from[0], segment.from[1], 0)
group.scale.set(factor, factor, factor)
}
}
updateSegment(sketchGroup.start, 0)
sgPaths.forEach(updateSegment)
})
})()
}
@ -668,7 +608,9 @@ class SceneEntities {
group.userData.from = from
group.userData.to = to
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const arrowGroup = group.children.find(
(child) => child.userData.type === ARROWHEAD
) as Group
arrowGroup.position.set(to[0], to[1], 0)
@ -743,20 +685,20 @@ class SceneEntities {
const shape = new Shape()
shape.moveTo(0, -0.08 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const arrowGroup = group.children.find(
(child) => child.userData.type === ARROWHEAD
) as Group
if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0)
arrowGroup.position.set(to[0], to[1], 0)
const dir = new Vector3()
.subVectors(
new Vector3(to[0], to[1], 0),
new Vector3(from[0], from[1], 0)
)
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
arrowGroup.scale.set(scale, scale, scale)
}
const dir = new Vector3()
.subVectors(
new Vector3(to[0], to[1], 0),
new Vector3(from[0], from[1], 0)
)
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
arrowGroup.scale.set(scale, scale, scale)
const straightSegmentBody = group.children.find(
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
@ -786,10 +728,10 @@ class SceneEntities {
}
async animateAfterSketch() {
if (isReducedMotion()) {
sceneInfra.camControls.usePerspectiveCamera()
return
sceneInfra.usePerspectiveCamera()
} else {
await sceneInfra.animateToPerspective()
}
await sceneInfra.camControls.animateToPerspective()
}
removeSketchGrid() {
if (this.axisGroup) this.scene.remove(this.axisGroup)
@ -821,7 +763,7 @@ class SceneEntities {
reject()
}
}
sceneInfra.camControls.enableRotate = true
sceneInfra.controls.enableRotate = true
this.activeSegments = {}
// maybe should reset onMove etc handlers
if (shouldResolve) resolve(true)
@ -841,24 +783,22 @@ class SceneEntities {
}
setupDefaultPlaneHover() {
sceneInfra.setCallbacks({
onMouseEnter: ({ selected }) => {
if (!(selected instanceof Mesh && selected.parent)) return
if (selected.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = selected.userData.type
selected.material.color = defaultPlaneColor(type, 0.5, 1)
onMouseEnter: ({ object }) => {
if (object.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = object.userData.type
object.material.color = defaultPlaneColor(type, 0.5, 1)
},
onMouseLeave: ({ selected }) => {
if (!(selected instanceof Mesh && selected.parent)) return
if (selected.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = selected.userData.type
selected.material.color = defaultPlaneColor(type)
onMouseLeave: ({ object }) => {
if (object.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = object.userData.type
object.material.color = defaultPlaneColor(type)
},
onClick: (args) => {
if (!args || !args.intersects?.[0]) return
if (args.mouseEvent.which !== 1) return
const { intersects } = args
const type = intersects?.[0].object.name || ''
const posNorm = Number(intersects?.[0]?.normal?.z) > 0
if (!args || !args.object) return
if (args.event.which !== 1) return
const { object, intersection } = args
const type = object?.userData?.type || ''
const posNorm = Number(intersection.normal?.z) > 0
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
if (type === YZ_PLANE) {
@ -1030,9 +970,9 @@ export function quaternionFromSketchGroup(
}
function colorSegment(object: any, color: number) {
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
if (segmentHead) {
segmentHead.traverse((child) => {
const arrowHead = getParentGroup(object, [ARROWHEAD])
if (arrowHead) {
arrowHead.traverse((child) => {
if (child instanceof Mesh) {
child.material.color.set(color)
}
@ -1088,53 +1028,3 @@ function massageFormats(a: any): Vector3 {
? new Vector3(a[0], a[1], a[2])
: new Vector3(a.x, a.y, a.z)
}
function mouseEnterLeaveCallbacks() {
return {
onMouseEnter: ({ selected }: OnMouseEnterLeaveArgs) => {
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
mat.color.offsetHSL(0, 0, 0.5)
}
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast))
const node = getNodeFromPath<CallExpression>(
updatedAst,
parent.userData.pathToNode,
'CallExpression'
).node
sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00
colorSegment(selected, yellow)
return
}
sceneInfra.highlightCallback([0, 0])
},
onMouseLeave: ({ selected }: OnMouseEnterLeaveArgs) => {
sceneInfra.highlightCallback([0, 0])
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
const isSelected = parent?.userData?.isSelected
colorSegment(
selected,
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
)
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
}
},
}
}

View File

@ -1,5 +1,5 @@
import { Quaternion } from 'three'
import { isQuaternionVertical } from './helpers'
import { isQuaternionVertical } from './sceneInfra'
describe('isQuaternionVertical', () => {
it('should identify vertical quaternions', () => {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
import { Coords2d } from 'lang/std/sketch'
import {
BoxGeometry,
BufferGeometry,
CatmullRomCurve3,
ConeGeometry,
@ -20,7 +19,6 @@ import {
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
import {
PROFILE_START,
STRAIGHT_SEGMENT,
STRAIGHT_SEGMENT_BODY,
STRAIGHT_SEGMENT_DASH,
@ -31,38 +29,6 @@ import {
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { ARROWHEAD } from './sceneInfra'
export function profileStart({
from,
id,
pathToNode,
scale = 1,
}: {
from: Coords2d
id: string
pathToNode: PathToNode
scale?: number
}) {
const group = new Group()
const geometry = new BoxGeometry(0.8, 0.8, 0.8)
const body = new MeshBasicMaterial({ color: 0xffffff })
const mesh = new Mesh(geometry, body)
group.add(mesh)
group.userData = {
type: PROFILE_START,
id,
from,
pathToNode,
isSelected: false,
}
group.name = PROFILE_START
group.position.set(from[0], from[1], 0)
group.scale.set(scale, scale, scale)
return group
}
export function straightSegment({
from,
to,
@ -70,7 +36,6 @@ export function straightSegment({
pathToNode,
isDraftSegment,
scale = 1,
callExpName,
}: {
from: Coords2d
to: Coords2d
@ -78,7 +43,6 @@ export function straightSegment({
pathToNode: PathToNode
isDraftSegment?: boolean
scale?: number
callExpName: string
}): Group {
const group = new Group()
@ -102,8 +66,7 @@ export function straightSegment({
})
}
const baseColor = callExpName === 'close' ? 0x444444 : 0xffffff
const body = new MeshBasicMaterial({ color: baseColor })
const body = new MeshBasicMaterial({ color: 0xffffff })
const mesh = new Mesh(geometry, body)
mesh.userData.type = isDraftSegment
? STRAIGHT_SEGMENT_DASH
@ -117,10 +80,7 @@ export function straightSegment({
to,
pathToNode,
isSelected: false,
callExpName,
baseColor,
}
group.name = STRAIGHT_SEGMENT
const arrowGroup = createArrowhead(scale)
arrowGroup.position.set(to[0], to[1], 0)
@ -129,8 +89,7 @@ export function straightSegment({
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
group.add(mesh)
if (callExpName !== 'close') group.add(arrowGroup)
group.add(mesh, arrowGroup)
return group
}
@ -210,7 +169,6 @@ export function tangentialArcToSegment({
pathToNode,
isSelected: false,
}
group.name = TANGENTIAL_ARC_TO_SEGMENT
const arrowGroup = createArrowhead(scale)
arrowGroup.position.set(to[0], to[1], 0)

View File

@ -87,7 +87,7 @@ export function useCalc({
inputRef: React.RefObject<HTMLInputElement>
valueNode: Value | null
calcResult: string
prevVariables: PrevVariable<unknown>[]
prevVariables: PrevVariable<any>[]
newVariableName: string
isNewVariableNameUnique: boolean
newVariableInsertIndex: number

View File

@ -4,7 +4,7 @@ import { engineCommandManager } from 'lang/std/engineConnection'
import { throttle, isReducedMotion } from 'lib/utils'
const updateDollyZoom = throttle(
(newFov: number) => sceneInfra.camControls.dollyZoom(newFov),
(newFov: number) => sceneInfra.dollyZoom(newFov),
1000 / 15
)
@ -15,19 +15,19 @@ export const CamToggle = () => {
useEffect(() => {
engineCommandManager.waitForReady.then(async () => {
sceneInfra.camControls.dollyZoom(fov)
sceneInfra.dollyZoom(fov)
})
}, [])
const toggleCamera = () => {
if (isPerspective) {
isReducedMotion()
? sceneInfra.camControls.useOrthographicCamera()
: sceneInfra.camControls.animateToOrthographic()
? sceneInfra.useOrthographicCamera()
: sceneInfra.animateToOrthographic()
} else {
isReducedMotion()
? sceneInfra.camControls.usePerspectiveCamera()
: sceneInfra.camControls.animateToPerspective()
? sceneInfra.usePerspectiveCamera()
: sceneInfra.animateToPerspective()
}
setIsPerspective(!isPerspective)
}
@ -60,9 +60,9 @@ export const CamToggle = () => {
<button
onClick={() => {
if (enableRotate) {
sceneInfra.camControls.enableRotate = false
sceneInfra.controls.enableRotate = false
} else {
sceneInfra.camControls.enableRotate = true
sceneInfra.controls.enableRotate = true
}
setEnableRotate(!enableRotate)
}}

View File

@ -1,8 +1,8 @@
import { Combobox } from '@headlessui/react'
import Fuse from 'fuse.js'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
import { useEffect, useMemo, useRef, useState } from 'react'
import { CommandArgumentOption } from 'lib/commandTypes'
import { useEffect, useRef, useState } from 'react'
function CommandArgOptionInput({
options,
@ -11,89 +11,51 @@ function CommandArgOptionInput({
onSubmit,
placeholder,
}: {
options: (CommandArgument<unknown> & { inputType: 'options' })['options']
options: CommandArgumentOption<unknown>[]
argName: string
stepBack: () => void
onSubmit: (data: unknown) => void
placeholder?: string
}) {
const { commandBarSend, commandBarState } = useCommandsContext()
const resolvedOptions = useMemo(
() =>
typeof options === 'function'
? options(commandBarState.context)
: options,
[argName, options, commandBarState.context]
)
// The initial current option is either an already-input value or the configured default
const currentOption = useMemo(
() =>
resolvedOptions.find(
(o) => o.value === commandBarState.context.argumentsToSubmit[argName]
) || resolvedOptions.find((o) => o.isCurrent),
[commandBarState.context.argumentsToSubmit, argName, resolvedOptions]
)
const inputRef = useRef<HTMLInputElement>(null)
const formRef = useRef<HTMLFormElement>(null)
const [selectedOption, setSelectedOption] = useState<
CommandArgumentOption<unknown>
>(currentOption || resolvedOptions[0])
const initialQuery = useMemo(() => '', [options, argName])
const [query, setQuery] = useState(initialQuery)
const [filteredOptions, setFilteredOptions] =
useState<typeof resolvedOptions>()
// Create a new Fuse instance when the options change
const fuse = useMemo(
() =>
new Fuse(resolvedOptions, {
keys: ['name', 'description'],
threshold: 0.3,
}),
[argName, resolvedOptions]
const [argValue, setArgValue] = useState<(typeof options)[number]['value']>(
options.find((o) => 'isCurrent' in o && o.isCurrent)?.value ||
commandBarState.context.argumentsToSubmit[argName] ||
options[0].value
)
const [query, setQuery] = useState('')
const [filteredOptions, setFilteredOptions] = useState<typeof options>()
// Reset the query and selected option when the argName changes
useEffect(() => {
setQuery(initialQuery)
setSelectedOption(currentOption || resolvedOptions[0])
}, [argName])
const fuse = new Fuse(options, {
keys: ['name', 'description'],
threshold: 0.3,
})
// Auto focus and select the input when the component mounts
useEffect(() => {
inputRef.current?.focus()
inputRef.current?.select()
}, [inputRef])
// Filter the options based on the query,
// resetting the query when the options change
useEffect(() => {
const results = fuse.search(query).map((result) => result.item)
setFilteredOptions(query.length > 0 ? results : resolvedOptions)
}, [query, resolvedOptions, fuse])
setFilteredOptions(query.length > 0 ? results : options)
}, [query])
function handleSelectOption(option: CommandArgumentOption<unknown>) {
// We deal with the whole option object internally
setSelectedOption(option)
// But we only submit the value
setArgValue(option)
onSubmit(option.value)
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
// We submit the value of the selected option, not the whole object
onSubmit(selectedOption.value)
onSubmit(argValue)
}
return (
<form id="arg-form" onSubmit={handleSubmit} ref={formRef}>
<Combobox
value={selectedOption}
onChange={handleSelectOption}
name="options"
>
<Combobox value={argValue} onChange={handleSelectOption} name="options">
<div className="flex items-center mx-4 mt-4 mb-2">
<label
htmlFor="option-input"
@ -113,12 +75,10 @@ function CommandArgOptionInput({
stepBack()
}
}}
value={query}
placeholder={
currentOption?.name ||
(argValue as CommandArgumentOption<unknown>)?.name ||
placeholder ||
argName ||
'Select an option'
'Select an option for ' + argName
}
autoCapitalize="off"
autoComplete="off"
@ -138,7 +98,7 @@ function CommandArgOptionInput({
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-energy-10/50 dark:ui-active:bg-chalkboard-90"
>
<p className="flex-grow">{option.name} </p>
{option.value === currentOption?.value && (
{'isCurrent' in option && option.isCurrent && (
<small className="text-chalkboard-70 dark:text-chalkboard-50">
current
</small>

View File

@ -26,17 +26,24 @@ export const CommandBarProvider = ({
children: React.ReactNode
}) => {
const { pathname } = useLocation()
const [commandBarState, commandBarSend] = useMachine(commandBarMachine, {
devTools: true,
guards: {
'Command has no arguments': (context, _event) => {
return (
!context.selectedCommand?.args ||
Object.keys(context.selectedCommand?.args).length === 0
)
const [commandBarState, commandBarSend] = useMachine(
commandBarMachine.provide({
guards: {
'Arguments are ready': ({ context }) => {
return context.selectedCommand?.args
? context.argumentsToSubmit.length ===
Object.keys(context.selectedCommand.args)?.length
: false
},
'Command has no arguments': ({ context }) => {
return (
!context.selectedCommand?.args ||
Object.keys(context.selectedCommand?.args).length === 0
)
},
},
},
})
})
)
// Close the command bar when navigating
useEffect(() => {
@ -51,11 +58,12 @@ export const CommandBarProvider = ({
}}
>
{children}
<CommandBar />
</CommandsContext.Provider>
)
}
export const CommandBar = () => {
const CommandBar = () => {
const { commandBarState, commandBarSend } = useCommandsContext()
const {
context: { selectedCommand, currentArgument, commands },
@ -75,23 +83,17 @@ export const CommandBar = () => {
function stepBack() {
if (!currentArgument) {
if (commandBarState.matches('Review')) {
const entries = Object.entries(selectedCommand?.args || {}).filter(
([_, argConfig]) =>
typeof argConfig.required === 'function'
? argConfig.required(commandBarState.context)
: argConfig.required
)
const currentArgName = entries[entries.length - 1][0]
const currentArg = {
name: currentArgName,
...entries[entries.length - 1][1],
}
const entries = Object.entries(selectedCommand?.args || {})
commandBarSend({
type: 'Edit argument',
type: commandBarState.matches('Review')
? 'Edit argument'
: 'Change current argument',
data: {
arg: currentArg,
arg: {
name: entries[entries.length - 1][0],
...entries[entries.length - 1][1],
},
},
})
} else {

View File

@ -4,7 +4,6 @@ import CommandBarSelectionInput from './CommandBarSelectionInput'
import { CommandArgument } from 'lib/commandTypes'
import { useCommandsContext } from 'hooks/useCommandsContext'
import CommandBarHeader from './CommandBarHeader'
import CommandBarKclInput from './CommandBarKclInput'
function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
const { commandBarState, commandBarSend } = useCommandsContext()
@ -18,7 +17,10 @@ function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
commandBarSend({
type: 'Submit argument',
data: {
[currentArgument.name]: data,
[currentArgument.name]:
currentArgument.inputType === 'number'
? parseFloat((data as string) || '0')
: data,
},
})
}
@ -66,10 +68,6 @@ function ArgumentInput({
onSubmit={onSubmit}
/>
)
case 'kcl':
return (
<CommandBarKclInput arg={arg} stepBack={stepBack} onSubmit={onSubmit} />
)
default:
return (
<CommandBarBasicInput

View File

@ -9,7 +9,7 @@ function CommandBarBasicInput({
onSubmit,
}: {
arg: CommandArgument<unknown> & {
inputType: 'string'
inputType: 'number' | 'string'
name: string
}
stepBack: () => void
@ -18,6 +18,7 @@ function CommandBarBasicInput({
const { commandBarSend, commandBarState } = useCommandsContext()
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
const inputRef = useRef<HTMLInputElement>(null)
const inputType = arg.inputType === 'number' ? 'number' : 'text'
useEffect(() => {
if (inputRef.current) {
@ -39,9 +40,9 @@ function CommandBarBasicInput({
</span>
<input
id="arg-form"
name={arg.inputType}
name={inputType}
ref={inputRef}
type={arg.inputType}
type={inputType}
required
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
placeholder="Enter a value"

View File

@ -4,9 +4,6 @@ import React, { ReactNode, useState } from 'react'
import { ActionButton } from '../ActionButton'
import { Selections, getSelectionTypeDisplayText } from 'lib/selections'
import { useHotkeys } from 'react-hotkeys-hook'
import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes'
import Tooltip from 'components/Tooltip'
import { roundOff } from 'lib/utils'
function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
const { commandBarState, commandBarSend } = useCommandsContext()
@ -48,7 +45,6 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
parseInt(b.keys[0], 10) - 1
]
const arg = selectedCommand?.args[argName]
if (!argName || !arg) return
commandBarSend({
type: 'Change current argument',
data: { arg: { ...arg, name: argName } },
@ -63,7 +59,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
selectedCommand &&
argumentsToSubmit && (
<>
<div className="group px-4 text-sm flex gap-4 items-start">
<div className="px-4 text-sm flex gap-4 items-start">
<div className="flex flex-1 flex-wrap gap-2">
<p
data-command-name={selectedCommand?.name}
@ -76,82 +72,47 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
)}
{selectedCommand?.name}
</p>
{Object.entries(selectedCommand?.args || {})
.filter(([_, argConfig]) =>
typeof argConfig.required === 'function'
? argConfig.required(commandBarState.context)
: argConfig.required
{Object.entries(selectedCommand?.args || {}).map(
([argName, arg], i) => (
<button
disabled={!isReviewing && currentArgument?.name === argName}
onClick={() => {
commandBarSend({
type: isReviewing
? 'Edit argument'
: 'Change current argument',
data: { arg: { ...arg, name: argName } },
})
}}
key={argName}
className={`relative w-fit px-2 py-1 rounded-sm flex gap-2 items-center border ${
argName === currentArgument?.name
? 'disabled:bg-energy-10/50 dark:disabled:bg-energy-10/20 disabled:border-energy-10 dark:disabled:border-energy-10 disabled:text-chalkboard-100 dark:disabled:text-chalkboard-10'
: 'bg-chalkboard-20/50 dark:bg-chalkboard-80/50 border-chalkboard-20 dark:border-chalkboard-80'
}`}
>
{argumentsToSubmit[argName] ? (
arg.inputType === 'selection' ? (
getSelectionTypeDisplayText(
argumentsToSubmit[argName] as Selections
)
) : typeof argumentsToSubmit[argName] === 'object' ? (
JSON.stringify(argumentsToSubmit[argName])
) : (
<em>{argumentsToSubmit[argName] as ReactNode}</em>
)
) : (
<em>{argName}</em>
)}
{showShortcuts && (
<small className="absolute -top-[1px] right-full translate-x-1/2 px-0.5 rounded-sm bg-chalkboard-80 text-chalkboard-10 dark:bg-energy-10 dark:text-chalkboard-100">
<span className="sr-only">Hotkey: </span>
{i + 1}
</small>
)}
</button>
)
.map(([argName, arg], i) => {
const argValue =
(typeof argumentsToSubmit[argName] === 'function'
? (argumentsToSubmit[argName] as Function)(
commandBarState.context
)
: argumentsToSubmit[argName]) || ''
return (
<button
disabled={!isReviewing && currentArgument?.name === argName}
onClick={() => {
commandBarSend({
type: isReviewing
? 'Edit argument'
: 'Change current argument',
data: { arg: { ...arg, name: argName } },
})
}}
key={argName}
className={`relative w-fit px-2 py-1 rounded-sm flex gap-2 items-center border ${
argName === currentArgument?.name
? 'disabled:bg-energy-10/50 dark:disabled:bg-energy-10/20 disabled:border-energy-10 dark:disabled:border-energy-10 disabled:text-chalkboard-100 dark:disabled:text-chalkboard-10'
: 'bg-chalkboard-20/50 dark:bg-chalkboard-80/50 border-chalkboard-20 dark:border-chalkboard-80'
}`}
>
<span className="capitalize">{argName}</span>
{argValue ? (
arg.inputType === 'selection' ? (
getSelectionTypeDisplayText(argValue as Selections)
) : arg.inputType === 'kcl' ? (
roundOff(
Number((argValue as KclCommandValue).valueCalculated),
4
)
) : typeof argValue === 'object' ? (
JSON.stringify(argValue)
) : (
<em>{argValue}</em>
)
) : null}
{showShortcuts && (
<small className="absolute -top-[1px] right-full translate-x-1/2 px-0.5 rounded-sm bg-chalkboard-80 text-chalkboard-10 dark:bg-energy-10 dark:text-chalkboard-100">
<span className="sr-only">Hotkey: </span>
{i + 1}
</small>
)}
{arg.inputType === 'kcl' &&
!!argValue &&
'variableName' in (argValue as KclCommandValue) && (
<>
<CustomIcon
name="make-variable"
className="w-4 h-4"
/>
<Tooltip position="blockEnd">
New variable:{' '}
{
(
argumentsToSubmit[
argName
] as KclExpressionWithVariable
).variableName
}
</Tooltip>
</>
)}
</button>
)
})}
)}
</div>
{isReviewing ? <ReviewingButton /> : <GatheringArgsButton />}
</div>

View File

@ -1,17 +0,0 @@
.editor {
@apply text-base flex-1;
}
.editor :global(.cm-editor) {
@apply bg-transparent;
}
.editor :global(.cm-line)::selection {
@apply px-1;
@apply text-chalkboard-100;
@apply bg-energy-10/50;
}
:global(.dark) .editor :global(.cm-line)::selection {
@apply text-energy-10;
@apply bg-energy-10/20;
}

View File

@ -1,221 +0,0 @@
import { Completion } from '@codemirror/autocomplete'
import { EditorState, EditorView, useCodeMirror } from '@uiw/react-codemirror'
import { CustomIcon } from 'components/CustomIcon'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
import { getSystemTheme } from 'lib/theme'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
import { roundOff } from 'lib/utils'
import { varMentions } from 'lib/varCompletionExtension'
import { useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import styles from './CommandBarKclInput.module.css'
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
function CommandBarKclInput({
arg,
stepBack,
onSubmit,
}: {
arg: CommandArgument<unknown> & {
inputType: 'kcl'
name: string
}
stepBack: () => void
onSubmit: (event: unknown) => void
}) {
const { commandBarSend, commandBarState } = useCommandsContext()
const previouslySetValue = commandBarState.context.argumentsToSubmit[
arg.name
] as KclCommandValue | undefined
const { settings } = useGlobalStateContext()
const defaultValue = (arg.defaultValue as string) || ''
const [value, setValue] = useState(
previouslySetValue?.valueText || defaultValue || ''
)
const [createNewVariable, setCreateNewVariable] = useState(
previouslySetValue && 'variableName' in previouslySetValue
)
const [canSubmit, setCanSubmit] = useState(true)
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
const editorRef = useRef<HTMLDivElement>(null)
const {
prevVariables,
calcResult,
newVariableInsertIndex,
valueNode,
newVariableName,
setNewVariableName,
isNewVariableNameUnique,
} = useCalculateKclExpression({
value,
initialVariableName:
previouslySetValue && 'variableName' in previouslySetValue
? previouslySetValue.variableName
: arg.name,
})
const varMentionData: Completion[] = prevVariables.map((v) => ({
label: v.key,
detail: String(roundOff(v.value as number)),
}))
const { setContainer } = useCodeMirror({
container: editorRef.current,
value,
indentWithTab: false,
basicSetup: false,
autoFocus: true,
selection: {
anchor: 0,
head:
previouslySetValue && 'valueText' in previouslySetValue
? previouslySetValue.valueText.length
: defaultValue.length,
},
accessKey: 'command-bar',
theme:
settings.context.theme === 'system'
? getSystemTheme()
: settings.context.theme,
extensions: [
EditorView.domEventHandlers({
keydown: (event) => {
if (event.key === 'Backspace' && value === '') {
event.preventDefault()
stepBack()
}
},
}),
varMentions(varMentionData),
EditorState.transactionFilter.of((tr) => {
if (tr.newDoc.lines > 1) {
handleSubmit()
return []
}
return tr
}),
],
onChange: (newValue) => setValue(newValue),
})
useEffect(() => {
if (editorRef.current) {
setContainer(editorRef.current)
}
}, [arg, editorRef])
useEffect(() => {
setCanSubmit(
calcResult !== 'NAN' && (!createNewVariable || isNewVariableNameUnique)
)
}, [calcResult, createNewVariable, isNewVariableNameUnique])
function handleSubmit(e?: React.FormEvent<HTMLFormElement>) {
e?.preventDefault()
if (!canSubmit || valueNode === null) return
onSubmit(
createNewVariable
? ({
valueAst: valueNode,
valueText: value,
valueCalculated: calcResult,
variableName: newVariableName,
insertIndex: newVariableInsertIndex,
variableIdentifierAst: createIdentifier(newVariableName),
variableDeclarationAst: createVariableDeclaration(
newVariableName,
valueNode
),
} satisfies KclCommandValue)
: ({
valueAst: valueNode,
valueText: value,
valueCalculated: calcResult,
} satisfies KclCommandValue)
)
}
return (
<form id="arg-form" onSubmit={handleSubmit} data-can-submit={canSubmit}>
<label className="flex gap-4 items-center mx-4 my-4 border-solid border-b border-chalkboard-50">
<span className="capitalize text-chalkboard-80 dark:text-chalkboard-20">
{arg.name}
</span>
<div ref={editorRef} className={styles.editor} />
<CustomIcon
name="equal"
className="w-5 h-5 text-chalkboard-70 dark:text-chalkboard-40"
/>
<span
className={
calcResult === 'NAN'
? 'text-destroy-80 dark:text-destroy-40'
: 'text-energy-60 dark:text-energy-20'
}
>
{calcResult === 'NAN'
? "Can't calculate"
: roundOff(Number(calcResult), 4)}
</span>
</label>
{createNewVariable ? (
<div className="flex items-baseline gap-4 mx-4 border-solid border-0 border-b border-chalkboard-50">
<label
htmlFor="variable-name"
className="text-base text-chalkboard-80 dark:text-chalkboard-20"
>
Variable name
</label>
<input
type="text"
id="variable-name"
name="variable-name"
className="flex-1 border-none bg-transparent"
placeholder="Variable name"
value={newVariableName}
autoCapitalize="off"
autoCorrect="off"
autoComplete="off"
spellCheck="false"
autoFocus
onChange={(e) => setNewVariableName(e.target.value)}
onKeyDown={(e) => {
if (e.currentTarget.value === '' && e.key === 'Backspace') {
setCreateNewVariable(false)
}
}}
onKeyUp={(e) => {
if (e.key === 'Enter') {
handleSubmit()
}
}}
/>
<span
className={
isNewVariableNameUnique
? 'text-energy-60 dark:text-energy-20'
: 'text-destroy-60 dark:text-destroy-40'
}
>
{isNewVariableNameUnique ? 'Available' : 'Unavailable'}
</span>
</div>
) : (
<div className="flex justify-between gap-2 px-4">
<button
onClick={() => setCreateNewVariable(true)}
className="text-blue border-none bg-transparent font-sm flex gap-1 items-center pl-0 pr-1"
>
<CustomIcon name="plus" className="w-5 h-5" />
Create new variable
</button>
</div>
)}
</form>
)
}
export default CommandBarKclInput

View File

@ -14,18 +14,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
})
useHotkeys(
[
'alt+1',
'alt+2',
'alt+3',
'alt+4',
'alt+5',
'alt+6',
'alt+7',
'alt+8',
'alt+9',
'alt+0',
],
'1, 2, 3, 4, 5, 6, 7, 8, 9, 0',
(_, b) => {
if (b.keys && !Number.isNaN(parseInt(b.keys[0], 10))) {
if (!selectedCommand?.args) return
@ -48,8 +37,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
if (!arg) return
})
function submitCommand(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
function submitCommand() {
commandBarSend({
type: 'Submit command',
data: argumentsToSubmit,

View File

@ -29,7 +29,7 @@ function CommandBarSelectionInput({
const inputRef = useRef<HTMLInputElement>(null)
const { commandBarState, commandBarSend } = useCommandsContext()
const [hasSubmitted, setHasSubmitted] = useState(false)
const selection = useSelector(arg.machineActor, selectionSelector)
const selection = useSelector(arg.actor, selectionSelector)
const [selectionsByType, setSelectionsByType] = useState<
'none' | ResolvedSelectionType[]
>(

View File

@ -1,5 +1,4 @@
export type CustomIconName =
| 'arc'
| 'arrowDown'
| 'arrowLeft'
| 'arrowRight'
@ -9,7 +8,6 @@ export type CustomIconName =
| 'clipboardCheckmark'
| 'close'
| 'equal'
| 'exportFile'
| 'extrude'
| 'file'
| 'filePlus'
@ -19,14 +17,11 @@ export type CustomIconName =
| 'horizontal'
| 'horizontalDash'
| 'line'
| 'make-variable'
| 'move'
| 'network'
| 'networkCrossedOut'
| 'parallel'
| 'plus'
| 'search'
| 'settings'
| 'sketch'
| 'vertical'
@ -37,22 +32,6 @@ export const CustomIcon = ({
name: CustomIconName
} & React.SVGProps<SVGSVGElement>) => {
switch (name) {
case 'arc':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 1.5C8.60217 1.5 7.22591 1.84474 5.99313 2.50367C4.76035 3.1626 3.70911 4.1154 2.93251 5.27765C2.15592 6.43991 1.67794 7.77575 1.54093 9.16685C1.40392 10.558 1.6121 11.9614 2.14703 13.2528C2.68195 14.5442 3.52712 15.6838 4.60766 16.5706C5.6882 17.4574 6.97076 18.064 8.34173 18.3367C9.71271 18.6094 11.1298 18.5398 12.4674 18.134C13.8051 17.7282 15.022 16.9988 16.0104 16.0104C16.3068 15.714 16.5796 15.3974 16.8273 15.0634L16.0241 14.4677C15.8055 14.7624 15.5648 15.0418 15.3033 15.3033C14.4312 16.1754 13.3574 16.819 12.1771 17.1771C10.9969 17.5351 9.74651 17.5965 8.53682 17.3559C7.32714 17.1153 6.19547 16.58 5.24205 15.7976C4.28863 15.0151 3.5429 14.0096 3.07091 12.8701C2.59891 11.7306 2.41522 10.4923 2.53612 9.26487C2.65701 8.03743 3.07875 6.85874 3.76398 5.83322C4.44921 4.8077 5.37678 3.967 6.46453 3.38559C7.55227 2.80418 8.76662 2.5 10 2.5C10.3699 2.5 10.7376 2.52734 11.1005 2.58117L11.2472 1.59199C10.836 1.53099 10.4192 1.5 10 1.5ZM13.2067 3.22008C13.5383 3.37691 13.8593 3.5585 14.1668 3.76398C14.4743 3.96946 14.7649 4.19652 15.0367 4.44286L15.7083 3.70191C15.4002 3.42271 15.0709 3.16538 14.7223 2.93251C14.3738 2.69964 14.0101 2.49384 13.6342 2.31609L13.2067 3.22008ZM16.433 6.14423C16.6216 6.45886 16.7876 6.78818 16.9291 7.12987C17.0706 7.47157 17.1861 7.82181 17.2752 8.17765L18.2453 7.93467C18.1443 7.53138 18.0134 7.13444 17.853 6.74719C17.6926 6.35995 17.5044 5.98672 17.2907 5.63012L16.433 6.14423ZM17.491 10.368C17.473 10.7344 17.428 11.1004 17.3559 11.4632C17.2837 11.8259 17.1852 12.1813 17.0616 12.5267L18.0031 12.8636C18.1432 12.4721 18.2549 12.0694 18.3367 11.6583C18.4184 11.2472 18.4694 10.8323 18.4898 10.4171L17.491 10.368Z"
fill="currentColor"
/>
</svg>
)
case 'arrowDown':
return (
<svg
@ -195,22 +174,6 @@ export const CustomIcon = ({
/>
</svg>
)
case 'exportFile':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4 3H4.5H11H11.2071L11.3536 3.14645L15.8536 7.64646L16 7.7929V8.00001V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V8.50001H11H10.5V8.00001V4H5V16H9.79076C9.93763 16.3628 10.1362 16.6992 10.3773 17H4.5H4V16.5V3.5V3ZM11.5 4.70711L14.2929 7.50001H11.5V4.70711ZM16.3904 14.1877L14.3904 11.6877L13.6096 12.3124L14.9597 14H11V15H14.9597L13.6096 16.6877L14.3904 17.3124L16.3904 14.8124L16.6403 14.5L16.3904 14.1877Z"
fill="currentColor"
/>
</svg>
)
case 'extrude':
return (
<svg
@ -355,22 +318,6 @@ export const CustomIcon = ({
/>
</svg>
)
case 'make-variable':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.07178 6.57735L9.99998 3.1547L15.9282 6.57735V13.4227L9.99998 16.8453L4.07178 13.4227V6.57735ZM9.99998 2L16.9282 6V14L9.99998 18L3.07178 14V6L9.99998 2ZM9.45068 6.854C9.20802 6.798 8.97468 6.78867 8.75068 6.826C8.39602 6.90067 8.06468 7.04533 7.75668 7.26C7.73802 7.26933 7.72402 7.27867 7.71468 7.288C7.45335 7.484 7.24802 7.694 7.09868 7.918C6.96802 8.09533 6.86068 8.282 6.77668 8.478C6.69268 8.65533 6.63668 8.814 6.60868 8.954C6.60868 9.00067 6.62268 9.038 6.65068 9.066L6.69268 9.108H6.95868C7.13602 9.108 7.23402 9.09867 7.25268 9.08C7.28068 9.052 7.30868 8.982 7.33668 8.87C7.45802 8.52467 7.65402 8.212 7.92468 7.932C8.13002 7.72667 8.36802 7.58667 8.63868 7.512C8.83468 7.456 9.02602 7.456 9.21268 7.512C9.40868 7.57733 9.53002 7.68467 9.57668 7.834C9.62335 7.96467 9.61402 8.198 9.54868 8.534L8.77868 11.614C8.65735 11.9593 8.47535 12.216 8.23268 12.384C8.10202 12.4587 7.97602 12.4913 7.85468 12.482C7.68668 12.482 7.53735 12.4307 7.40668 12.328L7.36468 12.286L7.42068 12.272C7.50468 12.244 7.57002 12.216 7.61668 12.188C7.93402 12.02 8.10668 11.7493 8.13468 11.376C8.15335 11.1053 8.05535 10.9187 7.84068 10.816C7.60735 10.6853 7.34135 10.69 7.04268 10.83C6.73468 10.9793 6.54802 11.2547 6.48268 11.656C6.45468 11.8893 6.47335 12.1087 6.53868 12.314C6.56668 12.4073 6.60868 12.4913 6.66468 12.566C6.92602 12.986 7.32268 13.182 7.85468 13.154C8.31202 13.126 8.72268 12.8787 9.08668 12.412L9.12868 12.37L9.21268 12.496C9.44602 12.8133 9.80068 13.0233 10.2767 13.126C10.5474 13.1633 10.79 13.1633 11.0047 13.126C11.6954 12.9767 12.2507 12.58 12.6707 11.936C12.6894 11.9173 12.7034 11.894 12.7127 11.866C12.9553 11.474 13.0767 11.18 13.0767 10.984C13.0767 10.9373 13.0674 10.9047 13.0487 10.886C13.0207 10.8673 12.918 10.858 12.7407 10.858C12.61 10.858 12.526 10.8627 12.4887 10.872C12.442 10.8813 12.4047 10.9327 12.3767 11.026C12.2834 11.3807 12.092 11.7073 11.8027 12.006C11.56 12.23 11.3174 12.3793 11.0747 12.454C11.0094 12.4727 10.9067 12.482 10.7667 12.482C10.6174 12.482 10.5194 12.4727 10.4727 12.454C10.314 12.398 10.1974 12.3 10.1227 12.16C10.0667 12.0573 10.062 11.8613 10.1087 11.572C10.1087 11.5347 10.132 11.4367 10.1787 11.278C10.58 9.542 10.8274 8.55733 10.9207 8.324C11.0887 7.88533 11.3127 7.61467 11.5927 7.512C11.6114 7.50267 11.63 7.498 11.6487 7.498C11.8914 7.43267 12.0967 7.47467 12.2647 7.624L12.3207 7.68L12.2087 7.722C11.8354 7.85267 11.6207 8.128 11.5647 8.548C11.5367 8.76267 11.5927 8.94 11.7327 9.08C11.77 9.11733 11.8167 9.15 11.8727 9.178C12.1714 9.32733 12.4887 9.28067 12.8247 9.038C12.9367 8.954 13.03 8.83267 13.1047 8.674C13.282 8.26333 13.2774 7.87133 13.0907 7.498C12.9787 7.26467 12.7874 7.078 12.5167 6.938C12.162 6.77933 11.8074 6.76533 11.4527 6.896C11.1447 7.01733 10.8787 7.20867 10.6547 7.47L10.5707 7.582C10.552 7.582 10.524 7.554 10.4867 7.498C10.2627 7.17133 9.91735 6.95667 9.45068 6.854Z"
fill="currentColor"
/>
</svg>
)
case 'move':
return (
<svg
@ -435,22 +382,6 @@ export const CustomIcon = ({
/>
</svg>
)
case 'plus':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.5 9.5V5.5H10.5V9.5H14.5V10.5H10.5V14.5H9.5V10.5H5.5V9.5H9.5Z"
fill="currentColor"
/>
</svg>
)
case 'search':
return (
<svg
@ -467,22 +398,6 @@ export const CustomIcon = ({
/>
</svg>
)
case 'settings':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 5.5C8 5.77614 7.77614 6 7.5 6C7.22386 6 7 5.77614 7 5.5C7 5.22386 7.22386 5 7.5 5C7.77614 5 8 5.22386 8 5.5ZM6.08535 6C6.29127 6.5826 6.84689 7 7.5 7C8.32843 7 9 6.32843 9 5.5C9 4.67157 8.32843 4 7.5 4C6.84689 4 6.29127 4.4174 6.08535 5H5V6H6.08535ZM15 6H9.94999C9.98278 5.83844 10 5.67123 10 5.5C10 5.32877 9.98278 5.16155 9.94999 5H15V6ZM11 14.5C11 14.7761 10.7761 15 10.5 15C10.2239 15 10 14.7761 10 14.5C10 14.2239 10.2239 14 10.5 14C10.7761 14 11 14.2239 11 14.5ZM9.08535 15C9.29127 15.5826 9.84689 16 10.5 16C11.3284 16 12 15.3284 12 14.5C12 13.6716 11.3284 13 10.5 13C9.84689 13 9.29127 13.4174 9.08535 14H5V15H9.08535ZM15 15H12.95C12.9828 14.8384 13 14.6712 13 14.5C13 14.3288 12.9828 14.1616 12.95 14H15V15ZM11.5 10.5C11.7761 10.5 12 10.2761 12 10C12 9.72386 11.7761 9.5 11.5 9.5C11.2239 9.5 11 9.72386 11 10C11 10.2761 11.2239 10.5 11.5 10.5ZM11.5 8.5C12.1531 8.5 12.7087 8.9174 12.9146 9.5H15V10.5H12.9146C12.7087 11.0826 12.1531 11.5 11.5 11.5C10.6716 11.5 10 10.8284 10 10C10 9.17157 10.6716 8.5 11.5 8.5ZM9.05001 10.5C9.01722 10.3384 9 10.1712 9 10C9 9.82877 9.01722 9.66155 9.05001 9.5H5V10.5H9.05001Z"
fill="currentColor"
/>
</svg>
)
case 'sketch':
return (
<svg

View File

@ -0,0 +1,238 @@
import { v4 as uuidv4 } from 'uuid'
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from './ActionButton'
import Modal from 'react-modal'
import React from 'react'
import { useFormik } from 'formik'
import { Models } from '@kittycad/lib'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
type OutputFormat = Models['OutputFormat_type']
type OutputTypeKey = OutputFormat['type']
type ExtractStorageTypes<T> = T extends { storage: infer U } ? U : never
type StorageUnion = ExtractStorageTypes<OutputFormat>
interface ExportButtonProps extends React.PropsWithChildren {
className?: {
button?: string
icon?: string
bg?: string
}
}
export const ExportButton = ({ children, className }: ExportButtonProps) => {
const [modalIsOpen, setIsOpen] = React.useState(false)
const {
settings: {
state: {
context: { baseUnit },
},
},
} = useGlobalStateContext()
const defaultType = 'gltf'
const [type, setType] = React.useState<OutputTypeKey>(defaultType)
const defaultStorage = 'embedded'
const [storage, setStorage] = React.useState<StorageUnion>(defaultStorage)
function openModal() {
setIsOpen(true)
}
function closeModal() {
setIsOpen(false)
}
// Default to gltf and embedded.
const initialValues: OutputFormat = {
type: defaultType,
storage: defaultStorage,
presentation: 'pretty',
}
const formik = useFormik({
initialValues,
onSubmit: (values: OutputFormat) => {
// Set the default coords.
if (
values.type === 'obj' ||
values.type === 'ply' ||
values.type === 'step' ||
values.type === 'stl'
) {
// Set the default coords.
// In the future we can make this configurable.
// But for now, its probably best to keep it consistent with the
// UI.
values.coords = {
forward: {
axis: 'y',
direction: 'negative',
},
up: {
axis: 'z',
direction: 'positive',
},
}
}
if (
values.type === 'obj' ||
values.type === 'stl' ||
values.type === 'ply'
) {
values.units = baseUnit
}
if (
values.type === 'ply' ||
values.type === 'stl' ||
values.type === 'gltf'
) {
// Set the storage type.
values.storage = storage
}
if (values.type === 'ply' || values.type === 'stl') {
values.selection = { type: 'default_scene' }
}
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'export',
// By default let's leave this blank to export the whole scene.
// In the future we might want to let the user choose which entities
// in the scene to export. In that case, you'd pass the IDs thru here.
entity_ids: [],
format: values,
source_unit: baseUnit,
},
cmd_id: uuidv4(),
})
closeModal()
},
})
return (
<>
<ActionButton
onClick={openModal}
Element="button"
icon={{
icon: faFileExport,
className: 'p-1',
size: 'sm',
iconClassName: className?.icon,
bgClassName: className?.bg,
}}
className={className?.button}
>
{children || 'Export'}
</ActionButton>
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="Export"
overlayClassName="z-40 fixed inset-0 grid place-items-center"
className="rounded p-4 bg-chalkboard-10 dark:bg-chalkboard-100 border max-w-xl w-full"
>
<h1 className="text-2xl font-bold">Export your design</h1>
<form onSubmit={formik.handleSubmit}>
<div className="flex flex-wrap justify-between gap-8 items-center w-full my-8">
<label htmlFor="type" className="flex-1">
<p className="mb-2">Type</p>
<select
id="type"
name="type"
data-testid="export-type"
onChange={(e) => {
setType(e.target.value as OutputTypeKey)
if (e.target.value === 'gltf') {
// Set default to embedded.
setStorage('embedded')
} else if (e.target.value === 'ply') {
// Set default to ascii.
setStorage('ascii')
} else if (e.target.value === 'stl') {
// Set default to ascii.
setStorage('ascii')
}
formik.handleChange(e)
}}
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
>
<option value="gltf">gltf</option>
<option value="obj">obj</option>
<option value="ply">ply</option>
<option value="step">step</option>
<option value="stl">stl</option>
</select>
</label>
{(type === 'gltf' || type === 'ply' || type === 'stl') && (
<label htmlFor="storage" className="flex-1">
<p className="mb-2">Storage</p>
<select
id="storage"
name="storage"
data-testid="export-storage"
onChange={(e) => {
setStorage(e.target.value as StorageUnion)
formik.handleChange(e)
}}
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
>
{type === 'gltf' && (
<>
<option value="embedded">embedded</option>
<option value="binary">binary</option>
<option value="standard">standard</option>
</>
)}
{type === 'stl' && (
<>
<option value="ascii">ascii</option>
<option value="binary">binary</option>
</>
)}
{type === 'ply' && (
<>
<option value="ascii">ascii</option>
<option value="binary_little_endian">
binary_little_endian
</option>
<option value="binary_big_endian">
binary_big_endian
</option>
</>
)}
</select>
</label>
)}
</div>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={closeModal}
icon={{
icon: faXmark,
className: 'p-1',
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Close
</ActionButton>
<ActionButton
Element="button"
type="submit"
icon={{ icon: faFileExport, className: 'p-1' }}
>
Export
</ActionButton>
</div>
</form>
</Modal>
</>
)
}

View File

@ -44,104 +44,103 @@ export const FileMachineProvider = ({
const { commandBarSend } = useCommandsContext()
const { project } = useRouteLoaderData(paths.FILE) as IndexLoaderData
const [state, send] = useMachine(fileMachine, {
context: {
project,
selectedDirectory: project,
},
actions: {
navigateToFile: (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine>
) => {
if (event.data && 'name' in event.data) {
commandBarSend({ type: 'Close' })
navigate(
`${paths.FILE}/${encodeURIComponent(
context.selectedDirectory + sep + event.data.name
)}`
)
}
const [state, send] = useMachine(
fileMachine.provide({
context: {
project,
selectedDirectory: project,
},
toastSuccess: (_, event) =>
event.data && toast.success((event.data || '') + ''),
toastError: (_, event) => toast.error((event.data || '') + ''),
},
services: {
readFiles: async (context: ContextFrom<typeof fileMachine>) => {
const newFiles = isTauri()
? await readProject(context.project.path)
: []
return {
...context.project,
children: newFiles,
}
actions: {
navigateToFile: ({ context, event }) => {
if (event.data && 'name' in event.data) {
commandBarSend({ type: 'Close' })
navigate(
`${paths.FILE}/${encodeURIComponent(
context.selectedDirectory + sep + event.data.name
)}`
)
}
},
toastSuccess: ({ event }) =>
event.data && toast.success((event.data || '') + ''),
toastError: ({ event }) => toast.error((event.data || '') + ''),
},
createFile: async (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine, 'Create file'>
) => {
let name = event.data.name.trim() || DEFAULT_FILE_NAME
services: {
readFiles: async (context: ContextFrom<typeof fileMachine>) => {
const newFiles = isTauri()
? await readProject(context.project.path)
: []
return {
...context.project,
children: newFiles,
}
},
createFile: async (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine, 'Create file'>
) => {
let name = event.data.name.trim() || DEFAULT_FILE_NAME
if (event.data.makeDir) {
await createDir(context.selectedDirectory.path + sep + name)
} else {
await writeFile(
if (event.data.makeDir) {
await createDir(context.selectedDirectory.path + sep + name)
} else {
await writeFile(
context.selectedDirectory.path +
sep +
name +
(name.endsWith(FILE_EXT) ? '' : FILE_EXT),
''
)
}
return `Successfully created "${name}"`
},
renameFile: async (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine, 'Rename file'>
) => {
const { oldName, newName, isDir } = event.data
let name = newName ? newName : DEFAULT_FILE_NAME
await renameFile(
context.selectedDirectory.path + sep + oldName,
context.selectedDirectory.path +
sep +
name +
(name.endsWith(FILE_EXT) ? '' : FILE_EXT),
''
(name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT)
)
}
return `Successfully created "${name}"`
},
renameFile: async (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine, 'Rename file'>
) => {
const { oldName, newName, isDir } = event.data
let name = newName ? newName : DEFAULT_FILE_NAME
await renameFile(
context.selectedDirectory.path + sep + oldName,
context.selectedDirectory.path +
sep +
name +
(name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT)
)
return (
oldName !== name && `Successfully renamed "${oldName}" to "${name}"`
)
},
deleteFile: async (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine, 'Delete file'>
) => {
const isDir = !!event.data.children
if (isDir) {
await removeDir(event.data.path, {
recursive: true,
}).catch((e) => console.error('Error deleting directory', e))
} else {
await removeFile(event.data.path).catch((e) =>
console.error('Error deleting file', e)
return (
oldName !== name && `Successfully renamed "${oldName}" to "${name}"`
)
}
return `Successfully deleted ${isDir ? 'folder' : 'file'} "${
event.data.name
}"`
},
deleteFile: async (
context: ContextFrom<typeof fileMachine>,
event: EventFrom<typeof fileMachine, 'Delete file'>
) => {
const isDir = !!event.data.children
if (isDir) {
await removeDir(event.data.path, {
recursive: true,
}).catch((e) => console.error('Error deleting directory', e))
} else {
await removeFile(event.data.path).catch((e) =>
console.error('Error deleting file', e)
)
}
return `Successfully deleted ${isDir ? 'folder' : 'file'} "${
event.data.name
}"`
},
},
},
guards: {
'Has at least 1 file': (_, event: EventFrom<typeof fileMachine>) => {
if (event.type !== 'done.invoke.read-files') return false
return !!event?.data?.children && event.data.children.length > 0
guards: {
'Has at least 1 file': ({ event }) => {
if (event.type !== 'done.invoke.read-files') return false
return !!event?.data?.children && event.data.children.length > 0
},
},
},
})
})
)
return (
<FileContext.Provider

View File

@ -18,7 +18,6 @@ import {
import { isTauri } from 'lib/isTauri'
import { settingsCommandBarConfig } from 'lib/commandBarConfigs/settingsCommandConfig'
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
import { sceneInfra } from 'clientSideScene/sceneInfra'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -31,12 +30,6 @@ type GlobalContext = {
settings: MachineContext<typeof settingsMachine>
}
// a little hacky for sure, open to changing it
// this implies that we should only even have one instance of this provider mounted at any one time
// but I think that's a safe assumption
let settingsStateRef: (typeof settingsMachine)['context'] | undefined
export const getSettingsState = () => settingsStateRef
export const GlobalStateContext = createContext({} as GlobalContext)
export const GlobalStateProvider = ({
@ -57,38 +50,33 @@ export const GlobalStateProvider = ({
>
)
const [settingsState, settingsSend, settingsActor] = useMachine(
settingsMachine,
{
context: persistedSettings,
actions: {
toastSuccess: (context, event) => {
const truncatedNewValue =
'data' in event && event.data instanceof Object
? (String(
context[Object.keys(event.data)[0] as keyof typeof context]
).substring(0, 28) as any)
: undefined
toast.success(
event.type +
(truncatedNewValue
? ` to "${truncatedNewValue}${
truncatedNewValue.length === 28 ? '...' : ''
}"`
: '')
)
},
const [settingsState, settingsSend] = useMachine(settingsMachine, {
context: persistedSettings,
actions: {
toastSuccess: (context, event) => {
const truncatedNewValue =
'data' in event && event.data instanceof Object
? (context[Object.keys(event.data)[0] as keyof typeof context]
.toString()
.substring(0, 28) as any)
: undefined
toast.success(
event.type +
(truncatedNewValue
? ` to "${truncatedNewValue}${
truncatedNewValue.length === 28 ? '...' : ''
}"`
: '')
)
},
}
)
settingsStateRef = settingsState.context
},
})
useStateMachineCommands({
machineId: 'settings',
state: settingsState,
send: settingsSend,
commandBarConfig: settingsCommandBarConfig,
actor: settingsActor,
})
// Listen for changes to the system theme and update the app theme accordingly
@ -102,14 +90,13 @@ export const GlobalStateProvider = ({
if (settingsState.context.theme !== 'system') return
setThemeClass(e.matches ? Themes.Dark : Themes.Light)
}
sceneInfra.baseUnit = settingsState?.context?.baseUnit || 'mm'
matcher.addEventListener('change', listener)
return () => matcher.removeEventListener('change', listener)
}, [settingsState.context])
// Auth machine setup
const [authState, authSend, authActor] = useMachine(authMachine, {
const [authState, authSend] = useMachine(authMachine, {
actions: {
goToSignInPage: () => {
navigate(paths.SIGN_IN)
@ -129,7 +116,6 @@ export const GlobalStateProvider = ({
state: authState,
send: authSend,
commandBarConfig: authCommandBarConfig,
actor: authActor,
})
return (

View File

@ -25,7 +25,8 @@ describe('processMemory', () => {
|> lineTo([-3.35, 0.17], %)
|> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %)
// |> rx(90, %)`
// |> rx(90, %)
show(theExtrude, theSketch)`
const ast = parse(code)
const programMemory = await enginelessExecutor(ast, {
root: {},

View File

@ -38,10 +38,6 @@ import { getSketchQuaternion } from 'clientSideScene/sceneEntities'
import { startSketchOnDefault } from 'lang/modifyAst'
import { Program } from 'lang/wasm'
import { isSingleCursorInPipe } from 'lang/queryAst'
import { TEST } from 'env'
import { exportFromEngine } from 'lib/exportFromEngine'
import { Models } from '@kittycad/lib/dist/types/src'
import toast from 'react-hot-toast'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -58,12 +54,7 @@ export const ModelingMachineProvider = ({
}: {
children: React.ReactNode
}) => {
const {
auth,
settings: {
context: { baseUnit },
},
} = useGlobalStateContext()
const { auth } = useGlobalStateContext()
const { code } = useKclContext()
const token = auth?.context?.token
const streamRef = useRef<HTMLDivElement>(null)
@ -94,7 +85,7 @@ export const ModelingMachineProvider = ({
'sketch exit execute': () => {
kclManager.executeAst()
},
'Set selection': assign(({ selectionRanges }, event) => {
'Set selection': assign(({ context: { selectionRanges }, event }) => {
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
const setSelections = event.data
if (!editorView) return {}
@ -179,59 +170,9 @@ export const ModelingMachineProvider = ({
}
return { selectionRangeTypeMap }
}),
'Engine export': (_, event) => {
if (event.type !== 'Export' || TEST) return
const format = {
...event.data,
} as Partial<Models['OutputFormat_type']>
// Set all the un-configurable defaults here.
if (format.type === 'gltf') {
format.presentation = 'pretty'
}
if (
format.type === 'obj' ||
format.type === 'ply' ||
format.type === 'step' ||
format.type === 'stl'
) {
// Set the default coords.
// In the future we can make this configurable.
// But for now, its probably best to keep it consistent with the
// UI.
format.coords = {
forward: {
axis: 'y',
direction: 'negative',
},
up: {
axis: 'z',
direction: 'positive',
},
}
}
if (
format.type === 'obj' ||
format.type === 'stl' ||
format.type === 'ply'
) {
format.units = baseUnit
}
if (format.type === 'ply' || format.type === 'stl') {
format.selection = { type: 'default_scene' }
}
exportFromEngine({
source_unit: baseUnit,
format: format as Models['OutputFormat_type'],
}).catch((e) => toast.error('Error while exporting', e)) // TODO I think we need to throw the error from engineCommandManager
},
},
guards: {
'has valid extrude selection': ({ selectionRanges }) => {
'has valid extrude selection': ({ context: { selectionRanges } }) => {
// A user can begin extruding if they either have 1+ faces selected or nothing selected
// TODO: I believe this guard only allows for extruding a single face at a time
if (selectionRanges.codeBasedSelections.length < 1) return false
@ -242,7 +183,10 @@ export const ModelingMachineProvider = ({
return canExtrudeSelection(selectionRanges)
},
'Selection is on face': ({ selectionRanges }, { data }) => {
'Selection is on face': ({
context: { selectionRanges },
event: { data },
}) => {
if (data?.forceNewSketch) return false
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
return false
@ -251,11 +195,9 @@ export const ModelingMachineProvider = ({
selectionRanges
)
},
'Has exportable geometry': () =>
kclManager.kclErrors.length === 0 && kclManager.ast.body.length > 0,
},
services: {
'AST-undo-startSketchOn': async ({ sketchPathToNode }) => {
'AST-undo-startSketchOn': async ({ context: { sketchPathToNode } }) => {
if (!sketchPathToNode) return
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
const varDecIndex = sketchPathToNode[1][0]
@ -267,31 +209,34 @@ export const ModelingMachineProvider = ({
onDrag: () => {},
})
},
'animate-to-face': async (_, { data: { plane, normal } }) => {
'animate-to-face': async ({
event: {
data: { plane, normal },
},
}) => {
const { modifiedAst, pathToNode } = startSketchOnDefault(
kclManager.ast,
plane
)
await kclManager.updateAst(modifiedAst, false)
const quaternion = getSketchQuaternion(pathToNode, normal)
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
await sceneInfra.tweenCameraToQuaternion(quaternion)
return {
sketchPathToNode: pathToNode,
sketchNormalBackUp: normal,
}
},
'animate-to-sketch': async ({
sketchPathToNode,
sketchNormalBackUp,
context: { sketchPathToNode, sketchNormalBackUp },
}) => {
const quaternion = getSketchQuaternion(
sketchPathToNode || [],
sketchNormalBackUp
)
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
await sceneInfra.tweenCameraToQuaternion(quaternion)
},
'Get horizontal info': async ({
selectionRanges,
context: { selectionRanges },
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
@ -309,7 +254,7 @@ export const ModelingMachineProvider = ({
}
},
'Get vertical info': async ({
selectionRanges,
context: { selectionRanges },
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
@ -327,7 +272,7 @@ export const ModelingMachineProvider = ({
}
},
'Get angle info': async ({
selectionRanges,
context: { selectionRanges },
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } = await (angleBetweenInfo({
selectionRanges,
@ -350,7 +295,7 @@ export const ModelingMachineProvider = ({
}
},
'Get length info': async ({
selectionRanges,
context: { selectionRanges },
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintAngleLength({ selectionRanges })
@ -365,7 +310,7 @@ export const ModelingMachineProvider = ({
}
},
'Get perpendicular distance info': async ({
selectionRanges,
context: { selectionRanges },
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } = await applyConstraintIntersect(
{
@ -383,7 +328,7 @@ export const ModelingMachineProvider = ({
}
},
'Get ABS X info': async ({
selectionRanges,
context: { selectionRanges },
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintAbsDistance({
@ -401,7 +346,7 @@ export const ModelingMachineProvider = ({
}
},
'Get ABS Y info': async ({
selectionRanges,
context: { selectionRanges },
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintAbsDistance({
@ -419,7 +364,8 @@ export const ModelingMachineProvider = ({
}
},
},
devTools: true,
// TODO replace with inspect https://stately.ai/docs/inspector
// devTools: true,
}
)
@ -435,7 +381,6 @@ export const ModelingMachineProvider = ({
send: modelingSend,
actor: modelingActor,
commandBarConfig: modelingMachineConfig,
allCommandsRequireNetwork: true,
onCancel: () => modelingSend({ type: 'Cancel' }),
})

View File

@ -80,7 +80,7 @@ const overallConnectionStateIcon: Record<
[NetworkHealthState.Disconnected]: 'networkCrossedOut',
}
export function useNetworkStatus() {
export const NetworkHealthIndicator = () => {
const [steps, setSteps] = useState(initialConnectingTypeGroupState)
const [internetConnected, setInternetConnected] = useState<boolean>(true)
const [overallState, setOverallState] = useState<NetworkHealthState>(
@ -118,18 +118,18 @@ export function useNetworkStatus() {
}, [hasIssues, internetConnected])
useEffect(() => {
const onlineCallback = () => {
const cb1 = () => {
setSteps(initialConnectingTypeGroupState)
setInternetConnected(true)
}
const offlineCallback = () => {
const cb2 = () => {
setInternetConnected(false)
}
window.addEventListener('online', onlineCallback)
window.addEventListener('offline', offlineCallback)
window.addEventListener('online', cb1)
window.addEventListener('offline', cb2)
return () => {
window.removeEventListener('online', onlineCallback)
window.removeEventListener('offline', offlineCallback)
window.removeEventListener('online', cb1)
window.removeEventListener('offline', cb2)
}
}, [])
@ -183,30 +183,6 @@ export function useNetworkStatus() {
)
}, [])
return {
hasIssues,
overallState,
internetConnected,
steps,
issues,
error,
setHasCopied,
hasCopied,
}
}
export const NetworkHealthIndicator = () => {
const {
hasIssues,
overallState,
internetConnected,
steps,
issues,
error,
setHasCopied,
hasCopied,
} = useNetworkStatus()
return (
<Popover className="relative">
<Popover.Button

View File

@ -3,8 +3,8 @@ import { BrowserRouter } from 'react-router-dom'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { type ProjectWithEntryPointMetadata } from 'lib/types'
import { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar/CommandBar'
import { APP_NAME } from 'lib/constants'
import { vi } from 'vitest'
const now = new Date()
const projectWellFormed = {
@ -41,9 +41,11 @@ describe('ProjectSidebarMenu tests', () => {
test('Renders the project name', () => {
render(
<BrowserRouter>
<GlobalStateProvider>
<ProjectSidebarMenu project={projectWellFormed} />
</GlobalStateProvider>
<CommandBarProvider>
<GlobalStateProvider>
<ProjectSidebarMenu project={projectWellFormed} />
</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)
@ -60,9 +62,11 @@ describe('ProjectSidebarMenu tests', () => {
test('Renders app name if given no project', () => {
render(
<BrowserRouter>
<GlobalStateProvider>
<ProjectSidebarMenu />
</GlobalStateProvider>
<CommandBarProvider>
<GlobalStateProvider>
<ProjectSidebarMenu />
</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)
@ -74,9 +78,14 @@ describe('ProjectSidebarMenu tests', () => {
test('Renders as a link if set to do so', () => {
render(
<BrowserRouter>
<GlobalStateProvider>
<ProjectSidebarMenu project={projectWellFormed} renderAsLink={true} />
</GlobalStateProvider>
<CommandBarProvider>
<GlobalStateProvider>
<ProjectSidebarMenu
project={projectWellFormed}
renderAsLink={true}
/>
</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)

View File

@ -5,12 +5,12 @@ import { type IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths'
import { isTauri } from '../lib/isTauri'
import { Link } from 'react-router-dom'
import { ExportButton } from './ExportButton'
import { Fragment } from 'react'
import { FileTree } from './FileTree'
import { sep } from '@tauri-apps/api/path'
import { Logo } from './Logo'
import { APP_NAME } from 'lib/constants'
import { useCommandsContext } from 'hooks/useCommandsContext'
const ProjectSidebarMenu = ({
project,
@ -21,8 +21,6 @@ const ProjectSidebarMenu = ({
project?: IndexLoaderData['project']
file?: IndexLoaderData['file']
}) => {
const { commandBarSend } = useCommandsContext()
return renderAsLink ? (
<Link
to={paths.HOME}
@ -114,19 +112,13 @@ const ProjectSidebarMenu = ({
<div className="flex-1 overflow-hidden" />
)}
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
<ActionButton
Element="button"
icon={{ icon: 'exportFile', className: 'p-1' }}
className="border-transparent dark:border-transparent"
onClick={() =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Export', ownerMachine: 'modeling' },
})
}
<ExportButton
className={{
button: 'border-transparent dark:border-transparent',
}}
>
Export Part
</ActionButton>
Export Model
</ExportButton>
{isTauri() && (
<ActionButton
Element="link"

View File

@ -5,10 +5,10 @@ import { Value } from '../lang/wasm'
import {
AvailableVars,
addToInputHelper,
useCalc,
CalcResult,
CreateNewVariable,
} from './AvailableVarsHelpers'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
type ModalResolve = {
value: string
@ -55,7 +55,7 @@ export const SetAngleLengthModal = ({
setNewVariableName,
inputRef,
newVariableInsertIndex,
} = useCalculateKclExpression({
} = useCalc({
value,
initialVariableName: valueName,
})

View File

@ -5,10 +5,10 @@ import { Value } from '../lang/wasm'
import {
AvailableVars,
addToInputHelper,
useCalc,
CalcResult,
CreateNewVariable,
} from './AvailableVarsHelpers'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
type ModalResolve = {
value: string
@ -59,7 +59,7 @@ export const GetInfoModal = ({
newVariableName,
isNewVariableNameUnique,
newVariableInsertIndex,
} = useCalculateKclExpression({ value: value, initialVariableName })
} = useCalc({ value: value, initialVariableName })
return (
<Transition appear show={isOpen} as={Fragment}>

View File

@ -1,11 +1,10 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment } from 'react'
import { CreateNewVariable } from './AvailableVarsHelpers'
import { useCalc, CreateNewVariable } from './AvailableVarsHelpers'
import { ActionButton } from './ActionButton'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { toast } from 'react-hot-toast'
import { type InstanceProps, create } from 'react-modal-promise'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
type ModalResolve = { variableName: string }
type ModalReject = boolean
@ -26,7 +25,7 @@ export const SetVarNameModal = ({
valueName,
}: SetVarNameModalProps) => {
const { isNewVariableNameUnique, newVariableName, setNewVariableName } =
useCalculateKclExpression({ value: '', initialVariableName: valueName })
useCalc({ value: '', initialVariableName: valueName })
return (
<Transition appear show={isOpen} as={Fragment}>

View File

@ -1,15 +1,21 @@
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
import {
MouseEventHandler,
WheelEventHandler,
useEffect,
useRef,
useState,
} from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
import { getNormalisedCoordinates } from '../lib/utils'
import { getNormalisedCoordinates, throttle } from '../lib/utils'
import Loading from './Loading'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { Models } from '@kittycad/lib'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
import { useKclContext } from 'lang/KclSingleton'
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
export const Stream = ({ className = '' }: { className?: string }) => {
const [isLoading, setIsLoading] = useState(true)
@ -29,10 +35,9 @@ export const Stream = ({ className = '' }: { className?: string }) => {
streamDimensions: s.streamDimensions,
}))
const { settings } = useGlobalStateContext()
const cameraControls = settings?.context?.cameraControls
const { state } = useModelingContext()
const { isExecuting } = useKclContext()
const { overallState } = useNetworkStatus()
const isNetworkOkay = overallState === NetworkHealthState.Ok
useEffect(() => {
if (
@ -60,6 +65,19 @@ export const Stream = ({ className = '' }: { className?: string }) => {
setClickCoords({ x, y })
}
const fps = 60
const handleScroll: WheelEventHandler<HTMLVideoElement> = throttle((e) => {
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
magnitude: e.deltaY * 0.4,
},
cmd_id: uuidv4(),
})
}, Math.round(1000 / fps))
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
clientX,
clientY,
@ -138,6 +156,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
muted
autoPlay
controls={false}
onWheel={handleScroll}
onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove}
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
@ -145,13 +164,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
/>
<ClientSideScene cameraControls={settings.context.cameraControls} />
{!isNetworkOkay && !isLoading && (
<div className="text-center absolute inset-0">
<Loading>
<span data-testid="loading-stream">Stream disconnected</span>
</Loading>
</div>
)}
{isLoading && (
<div className="text-center absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<Loading>

View File

@ -1,23 +1,22 @@
import { undo, redo } from '@codemirror/commands'
import ReactCodeMirror, {
Extension,
ViewUpdate,
keymap,
} from '@uiw/react-codemirror'
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
import Server from '../editor/plugins/lsp/server'
import Client from '../editor/plugins/lsp/client'
import { FromServer, IntoServer } from 'editor/lsp/codec'
import Server from '../editor/lsp/server'
import Client from '../editor/lsp/client'
import { TEST } from 'env'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { Themes } from 'lib/theme'
import { useEffect, useMemo, useRef } from 'react'
import { useMemo, useRef } from 'react'
import { linter, lintGutter } from '@codemirror/lint'
import { useStore } from 'useStore'
import { processCodeMirrorRanges } from 'lib/selections'
import { LanguageServerClient } from 'editor/plugins/lsp'
import kclLanguage from 'editor/plugins/lsp/kcl/language'
import { LanguageServerClient } from 'editor/lsp'
import kclLanguage from 'editor/lsp/language'
import { EditorView, lineHighlightField } from 'editor/highlightextension'
import { roundOff } from 'lib/utils'
import { kclErrToDiagnostic } from 'lang/errors'
@ -26,14 +25,9 @@ import { useModelingContext } from 'hooks/useModelingContext'
import interact from '@replit/codemirror-interact'
import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSingleton'
import { useFileContext } from 'hooks/useFileContext'
import { ModelingMachineEvent } from 'machines/modelingMachine'
import { sceneInfra } from 'clientSideScene/sceneInfra'
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
import { isTauri } from 'lib/isTauri'
import type * as LSP from 'vscode-languageserver-protocol'
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
import { useHotkeys } from 'react-hotkeys-hook'
import { copilotBundle } from 'editor/copilot'
export const editorShortcutMeta = {
formatCode: {
@ -46,15 +40,6 @@ export const editorShortcutMeta = {
},
}
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
// We only use workspace folders in Tauri since that is where we use more than
// one file.
if (isTauri()) {
return [{ uri: 'file://', name: 'ProjectRoot' }]
}
return []
}
export const TextEditor = ({
theme,
}: {
@ -79,28 +64,6 @@ export const TextEditor = ({
}))
const { code, errors } = useKclContext()
const lastEvent = useRef({ event: '', time: Date.now() })
const { overallState } = useNetworkStatus()
const isNetworkOkay = overallState === NetworkHealthState.Ok
useEffect(() => {
if (typeof window === 'undefined') return
const onlineCallback = () => kclManager.setCodeAndExecute(kclManager.code)
window.addEventListener('online', onlineCallback)
return () => window.removeEventListener('online', onlineCallback)
}, [])
useHotkeys('mod+z', (e) => {
e.preventDefault()
if (editorView) {
undo(editorView)
}
})
useHotkeys('mod+shift+z', (e) => {
e.preventDefault()
if (editorView) {
redo(editorView)
}
})
const {
context: { selectionRanges, selectionRangeTypeMap },
@ -111,9 +74,6 @@ export const TextEditor = ({
const { settings: { context: { textWrapping } = {} } = {}, auth } =
useGlobalStateContext()
const { commandBarSend } = useCommandsContext()
const {
context: { project },
} = useFileContext()
const { enable: convertEnabled, handleClick: convertCallback } =
useConvertToVariable()
@ -131,12 +91,12 @@ export const TextEditor = ({
})
}
const lspClient = new LanguageServerClient({ client, name: 'kcl' })
const lspClient = new LanguageServerClient({ client })
return { lspClient }
}, [setIsKclLspServerReady])
// Here we initialize the plugin which will start the client.
// Now that we have multi-file support the name of the file is a dep of
// When we have multi-file support the name of the file will be a dep of
// this use memo, as well as the directory structure, which I think is
// a good setup because it will restart the client but not the server :)
// We do not want to restart the server, its just wasteful.
@ -147,7 +107,7 @@ export const TextEditor = ({
const lsp = kclLanguage({
// When we have more than one file, we'll need to change this.
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
workspaceFolders: getWorkspaceFolders(),
workspaceFolders: null,
client: kclLspClient,
})
@ -168,7 +128,7 @@ export const TextEditor = ({
})
}
const lspClient = new LanguageServerClient({ client, name: 'copilot' })
const lspClient = new LanguageServerClient({ client })
return { lspClient }
}, [setIsCopilotLspServerReady])
@ -181,10 +141,10 @@ export const TextEditor = ({
let plugin = null
if (isCopilotLspServerReady && !TEST) {
// Set up the lsp plugin.
const lsp = copilotPlugin({
const lsp = copilotBundle({
// When we have more than one file, we'll need to change this.
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
workspaceFolders: getWorkspaceFolders(),
workspaceFolders: null,
client: copilotLspClient,
allowHTMLContent: true,
})
@ -192,12 +152,11 @@ export const TextEditor = ({
plugin = lsp
}
return plugin
}, [copilotLspClient, isCopilotLspServerReady, project])
}, [copilotLspClient, isCopilotLspServerReady])
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = async (newCode: string) => {
if (isNetworkOkay) kclManager.setCodeAndExecute(newCode)
else kclManager.setCode(newCode)
const onChange = (newCode: string) => {
kclManager.setCodeAndExecute(newCode)
} //, []);
const onUpdate = (viewUpdate: ViewUpdate) => {
if (!editorView) {

View File

@ -27,8 +27,6 @@ describe('UserSidebarMenu tests', () => {
phone: '555-555-5555',
first_name: 'Test',
last_name: 'User',
can_train_on_data: false,
is_service_account: false,
}
render(
@ -59,8 +57,6 @@ describe('UserSidebarMenu tests', () => {
first_name: '',
last_name: '',
name: '',
can_train_on_data: false,
is_service_account: false,
}
render(
@ -88,8 +84,6 @@ describe('UserSidebarMenu tests', () => {
first_name: 'Test',
last_name: 'User',
image: '',
can_train_on_data: false,
is_service_account: false,
}
render(

View File

@ -117,7 +117,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
<div className="p-4 flex flex-col gap-2">
<ActionButton
Element="button"
icon={{ icon: 'settings' }}
icon={{ icon: 'gear' }}
className="border-transparent dark:border-transparent hover:bg-transparent"
onClick={() => {
// since /settings is a nested route the sidebar doesn't close
@ -142,7 +142,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
</ActionButton>
<ActionButton
Element="externalLink"
to="https://github.com/KittyCAD/modeling-app/issues/new/choose"
to="https://github.com/KittyCAD/modeling-app/issues/new"
icon={{ icon: faBug, className: 'p-1', size: 'sm' }}
className="border-transparent dark:border-transparent"
>

View File

@ -11,20 +11,30 @@ import {
Annotation,
EditorState,
Extension,
Facet,
Prec,
StateEffect,
StateField,
Transaction,
} from '@codemirror/state'
import { completionStatus } from '@codemirror/autocomplete'
import { offsetToPos, posToOffset } from 'editor/plugins/lsp/util'
import { LanguageServerOptions, LanguageServerClient } from 'editor/plugins/lsp'
import {
LanguageServerPlugin,
documentUri,
languageId,
workspaceFolders,
} from 'editor/plugins/lsp/plugin'
import { docPathFacet, offsetToPos, posToOffset } from 'editor/lsp/util'
import { LanguageServerPlugin } from 'editor/lsp/plugin'
import { LanguageServerOptions } from 'editor/lsp/plugin'
import { LanguageServerClient } from 'editor/lsp'
// Create Facet for the current docPath
export const docPath = Facet.define<string, string>({
combine(value: readonly string[]) {
return value[value.length - 1]
},
})
export const relDocPath = Facet.define<string, string>({
combine(value: readonly string[]) {
return value[value.length - 1]
},
})
const ghostMark = Decoration.mark({ class: 'cm-ghostText' })
@ -351,9 +361,9 @@ const completionRequester = (client: LanguageServerClient) => {
const pos = state.selection.main.head
const source = state.doc.toString()
const dUri = state.facet(documentUri)
const path = dUri.split('/').pop()!
const relativePath = dUri.replace('file://', '')
const path = state.facet(docPath)
const relativePath = state.facet(relDocPath)
const languageId = 'kcl'
// Set a new timeout to request completion
timeout = setTimeout(async () => {
@ -368,9 +378,9 @@ const completionRequester = (client: LanguageServerClient) => {
indentSize: 1,
insertSpaces: true,
path,
uri: dUri,
uri: `file://${path}`,
relativePath,
languageId: state.facet(languageId),
languageId,
position: offsetToPos(state.doc, pos),
},
})
@ -473,24 +483,21 @@ const completionRequester = (client: LanguageServerClient) => {
})
}
export const copilotPlugin = (options: LanguageServerOptions): Extension => {
let plugin: LanguageServerPlugin | null = null
return [
documentUri.of(options.documentUri),
languageId.of('kcl'),
workspaceFolders.of(options.workspaceFolders),
ViewPlugin.define(
(view) =>
(plugin = new LanguageServerPlugin(
options.client,
view,
options.allowHTMLContent
))
),
completionDecoration,
Prec.highest(completionPlugin(options.client)),
Prec.highest(viewCompletionPlugin(options.client)),
completionRequester(options.client),
]
export function copilotServer(options: LanguageServerOptions) {
let plugin: LanguageServerPlugin
return ViewPlugin.define(
(view) =>
(plugin = new LanguageServerPlugin(view, options.allowHTMLContent))
)
}
export const copilotBundle = (options: LanguageServerOptions): Extension => [
docPath.of(options.documentUri.split('/').pop()!),
docPathFacet.of(options.documentUri.split('/').pop()!),
relDocPath.of(options.documentUri.replace('file://', '')),
completionDecoration,
Prec.highest(completionPlugin(options.client)),
Prec.highest(viewCompletionPlugin(options.client)),
completionRequester(options.client),
copilotServer(options),
]

View File

@ -65,7 +65,6 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
afterInitializedHooks: (() => Promise<void>)[] = []
#fromServer: FromServer
private serverCapabilities: LSP.ServerCapabilities<any> = {}
private notifyFn: ((message: LSP.NotificationMessage) => void) | null = null
constructor(fromServer: FromServer, intoServer: IntoServer) {
super(
@ -168,15 +167,9 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
return this.serverCapabilities
}
setNotifyFn(fn: (message: LSP.NotificationMessage) => void): void {
this.notifyFn = fn
}
async processNotifications(): Promise<void> {
for await (const notification of this.#fromServer.notifications) {
if (this.notifyFn) {
this.notifyFn(notification)
}
await this.receiveAndSend(notification)
}
}

View File

@ -1,7 +1,7 @@
import type * as LSP from 'vscode-languageserver-protocol'
import Client from './client'
import { SemanticToken, deserializeTokens } from './kcl/semantic_tokens'
import { LanguageServerPlugin } from 'editor/plugins/lsp/plugin'
import { LanguageServerPlugin } from './plugin'
import { SemanticToken, deserializeTokens } from './semantic_tokens'
export interface CopilotGetCompletionsParams {
doc: {
@ -90,22 +90,26 @@ interface LSPNotifyMap {
'textDocument/didOpen': LSP.DidOpenTextDocumentParams
}
export interface LanguageServerClientOptions {
client: Client
name: string
// Server to client
interface LSPEventMap {
'textDocument/publishDiagnostics': LSP.PublishDiagnosticsParams
}
export interface LanguageServerOptions {
// We assume this is the main project directory, we are currently working in.
workspaceFolders: LSP.WorkspaceFolder[]
documentUri: string
allowHTMLContent: boolean
client: LanguageServerClient
export type Notification = {
[key in keyof LSPEventMap]: {
jsonrpc: '2.0'
id?: null | undefined
method: key
params: LSPEventMap[key]
}
}[keyof LSPEventMap]
export interface LanguageServerClientOptions {
client: Client
}
export class LanguageServerClient {
private client: Client
private name: string
public ready: boolean
@ -120,7 +124,6 @@ export class LanguageServerClient {
constructor(options: LanguageServerClientOptions) {
this.plugins = []
this.client = options.client
this.name = options.name
this.ready = false
@ -130,16 +133,11 @@ export class LanguageServerClient {
async initialize() {
// Start the client in the background.
this.client.setNotifyFn(this.processNotifications.bind(this))
this.client.start()
this.ready = true
}
getName(): string {
return this.name
}
getServerCapabilities(): LSP.ServerCapabilities<any> {
return this.client.getServerCapabilities()
}
@ -158,11 +156,6 @@ export class LanguageServerClient {
}
async updateSemanticTokens(uri: string) {
const serverCapabilities = this.getServerCapabilities()
if (!serverCapabilities.semanticTokensProvider) {
return
}
// Make sure we can only run, if we aren't already running.
if (!this.isUpdatingSemanticTokens) {
this.isUpdatingSemanticTokens = true
@ -187,18 +180,10 @@ export class LanguageServerClient {
}
async textDocumentHover(params: LSP.HoverParams) {
const serverCapabilities = this.getServerCapabilities()
if (!serverCapabilities.hoverProvider) {
return
}
return await this.request('textDocument/hover', params)
}
async textDocumentCompletion(params: LSP.CompletionParams) {
const serverCapabilities = this.getServerCapabilities()
if (!serverCapabilities.completionProvider) {
return
}
return await this.request('textDocument/completion', params)
}
@ -249,12 +234,11 @@ export class LanguageServerClient {
async acceptCompletion(params: CopilotAcceptCompletionParams) {
return await this.request('notifyAccepted', params)
}
async rejectCompletions(params: CopilotRejectCompletionParams) {
return await this.request('notifyRejected', params)
}
private processNotifications(notification: LSP.NotificationMessage) {
private processNotification(notification: Notification) {
for (const plugin of this.plugins) plugin.processNotification(notification)
}
}

View File

@ -5,8 +5,8 @@ import {
defineLanguageFacet,
LanguageSupport,
} from '@codemirror/language'
import { LanguageServerClient } from 'editor/plugins/lsp'
import { kclPlugin } from '.'
import { LanguageServerClient } from '.'
import { kclPlugin } from './plugin'
import type * as LSP from 'vscode-languageserver-protocol'
import { parser as jsParser } from '@lezer/javascript'
import { EditorState } from '@uiw/react-codemirror'
@ -14,7 +14,7 @@ import { EditorState } from '@uiw/react-codemirror'
const data = defineLanguageFacet({})
export interface LanguageOptions {
workspaceFolders: LSP.WorkspaceFolder[]
workspaceFolders: LSP.WorkspaceFolder[] | null
documentUri: string
client: LanguageServerClient
}

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