Compare commits
227 Commits
derive-doc
...
v0.15.5
Author | SHA1 | Date | |
---|---|---|---|
7d7b176bb7 | |||
9aada41a0d | |||
23971465ce | |||
23e294930b | |||
22cc4c9a98 | |||
fe6478f568 | |||
1989734c3b | |||
f36984f52a | |||
5437538892 | |||
97bd60ae87 | |||
9116d79c50 | |||
b3b5dff60f | |||
55f842d3bd | |||
778478757e | |||
bc303fbaab | |||
d422f09045 | |||
adcf80331a | |||
4fbd7ace98 | |||
0df858b9ca | |||
c6f080c440 | |||
c1a14a107a | |||
3c721f2b29 | |||
61e2a1eddc | |||
6406e27794 | |||
1e382a76dd | |||
06cdaa9ae8 | |||
85c30be333 | |||
4d4a1d66e8 | |||
223b5952aa | |||
fedffbb384 | |||
ed4e3df3b2 | |||
18d200e790 | |||
0c50a5996d | |||
73bca2dcfc | |||
c6a50a3cdf | |||
b81c9d04cc | |||
9d8a7064da | |||
b0e6140e9f | |||
f9df7ff885 | |||
aec9637d7a | |||
e4c5fad8c7 | |||
cc0d601294 | |||
69cefafc19 | |||
b187ca3422 | |||
1edadcaa0f | |||
95c0ded8cf | |||
0ebb4e2cad | |||
f3e0939057 | |||
f5e233d8a0 | |||
1cab3e628f | |||
2ca6ba52b6 | |||
f741ea2e09 | |||
9f2a7781fc | |||
990f2b4154 | |||
0af0f15281 | |||
b558548b94 | |||
29e0f9a270 | |||
9385c32cfb | |||
ce3fb5c353 | |||
f920490518 | |||
d681e667ee | |||
5c6515a60e | |||
eb8a33312d | |||
d351b3bbe4 | |||
47d40eb801 | |||
adc4b6148d | |||
27d0d4a28b | |||
fb609c19ef | |||
8666989c85 | |||
bdf49c2084 | |||
a06b9d560a | |||
b81ff66f2b | |||
c0e6947170 | |||
65ebde0b34 | |||
0d6618b60a | |||
f0c44d11b3 | |||
44e71cd4bc | |||
a9f716dad8 | |||
a2455832e7 | |||
8f5034f997 | |||
af1c2c7ae1 | |||
ff38ae091e | |||
1dd7c95b8c | |||
20042ec87c | |||
fccf3508a7 | |||
8dab5527b8 | |||
f72eb0e8a7 | |||
40479d177f | |||
b88359dee2 | |||
f4c0347104 | |||
ad36b5f5fa | |||
b798cf19d3 | |||
7cfa897561 | |||
0d8804005a | |||
cbd26d29fa | |||
e501a542ac | |||
7cb4f4d101 | |||
1162f5f4c4 | |||
3975e6d8f5 | |||
d68d7a7e00 | |||
b135b97de6 | |||
de5885ce0b | |||
ad7c544754 | |||
4d77875bdc | |||
3377923dcb | |||
c6005660c8 | |||
66e62c6037 | |||
0a4a517bb4 | |||
70f3ded7e2 | |||
095108252b | |||
20b1c93f12 | |||
3747a1b993 | |||
198feb7d44 | |||
c7a8b8313e | |||
1576dc3256 | |||
341a3b7609 | |||
ecb42b89a6 | |||
f00ee3a44a | |||
900e3b96ad | |||
15fae05659 | |||
2730b6d152 | |||
602e7afef6 | |||
d9bcadb062 | |||
19f669b94c | |||
d9ef471385 | |||
39f8b306a2 | |||
19925d22c1 | |||
e1af4b4219 | |||
c699611f5b | |||
00ede7ec1a | |||
f30601bd2c | |||
cfbc77b62f | |||
808830d29e | |||
e714103655 | |||
fbcb96add5 | |||
7386ccf1bf | |||
6e73578933 | |||
b88d5c8799 | |||
5430c1fa66 | |||
c0d4bb6c9f | |||
25260a88c3 | |||
b6d6f0f4c1 | |||
b1276b2ed8 | |||
5f0f3f40d0 | |||
f1ea9b6ece | |||
b94c5be1af | |||
8378eb1e94 | |||
05f98a8c39 | |||
386571fa60 | |||
b0abdf4f70 | |||
81e70e139f | |||
d6bfc38d62 | |||
ada66de92d | |||
8f133f9662 | |||
b360dbb961 | |||
eca3dc2967 | |||
ae36ab6982 | |||
8cb6cf1b8a | |||
3c235c890a | |||
b6dfd30840 | |||
65d128eecd | |||
77b7c602f2 | |||
fa0e61a2be | |||
1cf35a611e | |||
952d0e4c7c | |||
0f85de9df8 | |||
0e8eed3f82 | |||
5b43a5075f | |||
f5ed4e37b2 | |||
19c8da1a86 | |||
a25f89aaba | |||
aeebe5416f | |||
661788b8b0 | |||
ac24563159 | |||
d17342dfb8 | |||
2e93b58ae6 | |||
6593656b08 | |||
47be749ec7 | |||
a03e7f5c41 | |||
b78e9fa131 | |||
c629233eaa | |||
f640f7a5e0 | |||
64398381a9 | |||
0bc5534056 | |||
9fc1df7c1d | |||
a5879ceeda | |||
379c30824e | |||
a4d3263b88 | |||
c1f661ab52 | |||
7d887a1497 | |||
4ca341e132 | |||
c6249f36d2 | |||
dcbe5d7f75 | |||
390cb2d51d | |||
98f7a564ea | |||
05f9e3c290 | |||
09760fc2e9 | |||
18ffc43e89 | |||
de63e4f19f | |||
b70b271e6b | |||
08b7cdc5f6 | |||
6efe6b54c0 | |||
69f72d62e0 | |||
e04b09fcd8 | |||
4903f6b9fc | |||
ef8149f03a | |||
1b75321bf1 | |||
3ed263da6b | |||
d59c4a2258 | |||
9c8351ea40 | |||
db98bcf2a0 | |||
15d96a072d | |||
088968c664 | |||
4bbf98bc34 | |||
ca08f5b337 | |||
a3649d09c0 | |||
635cb58036 | |||
7f050b114f | |||
c999819450 | |||
82905caad6 | |||
519e6d74ac | |||
edb7d68c05 | |||
345dd45caa | |||
b6a5f133f3 | |||
bc6407be6e | |||
038409124a | |||
d5567f8602 |
@ -1,3 +1,3 @@
|
|||||||
[codespell]
|
[codespell]
|
||||||
ignore-words-list: crate,everytime
|
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo
|
||||||
skip: **/target,node_modules,build
|
skip: **/target,node_modules,build,**/Cargo.lock
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
|
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
||||||
VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
|
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
VITE_KC_SENTRY_DSN=
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.kittycad.io
|
VITE_KC_API_BASE_URL=https://api.zoo.dev
|
||||||
VITE_KC_SITE_BASE_URL=https://kittycad.io
|
VITE_KC_SITE_BASE_URL=https://zoo.dev
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
||||||
VITE_KC_SENTRY_DSN=https://a814f2f66734989a90367f48feee28ca@o1042111.ingest.sentry.io/4505789425844224
|
|
||||||
|
@ -1 +1,2 @@
|
|||||||
src/wasm-lib/*
|
src/wasm-lib/*
|
||||||
|
*.typegen.ts
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
"never"
|
"never"
|
||||||
],
|
],
|
||||||
"react-hooks/exhaustive-deps": "off",
|
"react-hooks/exhaustive-deps": "off",
|
||||||
"@typescript-eslint/no-floating-promises": "warn"
|
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
|
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"@typescript-eslint/no-floating-promises": "warn",
|
||||||
"testing-library/prefer-screen-queries": "off"
|
"testing-library/prefer-screen-queries": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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
|
11
.github/workflows/cargo-clippy.yml
vendored
@ -43,17 +43,6 @@ jobs:
|
|||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.6.1
|
uses: Swatinem/rust-cache@v2.6.1
|
||||||
|
|
||||||
- name: Install ffmpeg
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install \
|
|
||||||
ffmpeg \
|
|
||||||
libavformat-dev \
|
|
||||||
libavutil-dev \
|
|
||||||
libclang-dev \
|
|
||||||
libswscale-dev \
|
|
||||||
--no-install-recommends
|
|
||||||
|
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: |
|
run: |
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
|
26
.github/workflows/cargo-test.yml
vendored
@ -40,25 +40,29 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
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@cargo-llvm-cov
|
||||||
- uses: taiki-e/install-action@nextest
|
- uses: taiki-e/install-action@nextest
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.6.1
|
uses: Swatinem/rust-cache@v2.6.1
|
||||||
- name: Install ffmpeg
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install \
|
|
||||||
ffmpeg \
|
|
||||||
libavformat-dev \
|
|
||||||
libavutil-dev \
|
|
||||||
libclang-dev \
|
|
||||||
libswscale-dev \
|
|
||||||
--no-install-recommends
|
|
||||||
- name: cargo test
|
- name: cargo test
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo nextest run --workspace --no-fail-fast -P ci
|
cargo nextest run --workspace --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
|
||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
RUST_MIN_STACK: 10485760000
|
RUST_MIN_STACK: 10485760000
|
||||||
|
90
.github/workflows/ci.yml
vendored
@ -46,6 +46,7 @@ jobs:
|
|||||||
workspaces: './src/wasm-lib'
|
workspaces: './src/wasm-lib'
|
||||||
|
|
||||||
- run: yarn build:wasm
|
- run: yarn build:wasm
|
||||||
|
- run: yarn xstate:typegen
|
||||||
- run: yarn tsc
|
- run: yarn tsc
|
||||||
|
|
||||||
|
|
||||||
@ -85,8 +86,6 @@ jobs:
|
|||||||
|
|
||||||
- run: yarn test:nowatch
|
- run: yarn test:nowatch
|
||||||
|
|
||||||
- run: yarn test:cov
|
|
||||||
|
|
||||||
|
|
||||||
prepare-json-files:
|
prepare-json-files:
|
||||||
runs-on: ubuntu-latest # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
runs-on: ubuntu-latest # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||||
@ -104,7 +103,7 @@ jobs:
|
|||||||
if: github.event_name == 'schedule'
|
if: github.event_name == 'schedule'
|
||||||
run: |
|
run: |
|
||||||
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
|
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
|
||||||
echo "$(jq --arg url 'https://dl.kittycad.io/releases/modeling-app/nightly/last_update.json' \
|
echo "$(jq --arg url 'https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json' \
|
||||||
'.tauri.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
'.tauri.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
@ -123,8 +122,9 @@ jobs:
|
|||||||
needs: [prepare-json-files]
|
needs: [prepare-json-files]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
os: [macos-14, ubuntu-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@ -187,10 +187,10 @@ jobs:
|
|||||||
- name: Fix format
|
- name: Fix format
|
||||||
run: yarn fmt
|
run: yarn fmt
|
||||||
|
|
||||||
- name: Install Universal target (MacOS only)
|
- name: Install x86 target for Universal builds (MacOS only)
|
||||||
if: matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-14'
|
||||||
run: |
|
run: |
|
||||||
rustup target add aarch64-apple-darwin
|
rustup target add x86_64-apple-darwin
|
||||||
|
|
||||||
- name: Prepare certificate and variables (Windows only)
|
- name: Prepare certificate and variables (Windows only)
|
||||||
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
|
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
|
||||||
@ -224,7 +224,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
includeRelease: false
|
includeRelease: false
|
||||||
includeDebug: true
|
includeDebug: true
|
||||||
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
|
args: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
|
||||||
|
|
||||||
- name: Build the app (release) and sign
|
- name: Build the app (release) and sign
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
@ -240,11 +240,12 @@ jobs:
|
|||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
TAURI_CONF_ARGS: "--config ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
|
TAURI_CONF_ARGS: "--config ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
|
||||||
with:
|
with:
|
||||||
args: "${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}"
|
args: "${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}"
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: matrix.os != 'ubuntu-latest'
|
||||||
env:
|
env:
|
||||||
PREFIX: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }}
|
PREFIX: ${{ matrix.os == 'macos-14' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }}
|
||||||
MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}
|
MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}
|
||||||
with:
|
with:
|
||||||
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
||||||
@ -252,12 +253,12 @@ jobs:
|
|||||||
- name: Run e2e tests (linux only)
|
- name: Run e2e tests (linux only)
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
cargo install tauri-driver
|
cargo install tauri-driver@0.1.3
|
||||||
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||||
export VITE_KC_API_BASE_URL
|
export VITE_KC_API_BASE_URL
|
||||||
xvfb-run yarn test:e2e:tauri
|
xvfb-run yarn test:e2e:tauri
|
||||||
env:
|
env:
|
||||||
E2E_APPLICATION: "./src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/kittycad-modeling"
|
E2E_APPLICATION: "./src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/zoo-modeling-app"
|
||||||
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
|
|
||||||
|
|
||||||
@ -271,26 +272,24 @@ jobs:
|
|||||||
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
||||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }}
|
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }}
|
||||||
BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }}
|
BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }}
|
||||||
|
WEBSITE_DIR: ${{ github.event_name == 'release' && 'dl.zoo.dev/releases/modeling-app' || 'dl.zoo.dev/releases/modeling-app/nightly' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
|
|
||||||
- name: Generate the update static endpoint
|
- name: Generate the update static endpoint
|
||||||
run: |
|
run: |
|
||||||
ls -l artifact/*/*itty*
|
ls -l artifact/*/*oo*
|
||||||
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
||||||
LINUX_SIG=`cat artifact/appimage/*.AppImage.tar.gz.sig`
|
|
||||||
WINDOWS_SIG=`cat artifact/msi/*.msi.zip.sig`
|
WINDOWS_SIG=`cat artifact/msi/*.msi.zip.sig`
|
||||||
RELEASE_DIR=https://${BUCKET_DIR}/${VERSION}
|
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
||||||
jq --null-input \
|
jq --null-input \
|
||||||
--arg version "${VERSION}" \
|
--arg version "${VERSION}" \
|
||||||
--arg pub_date "${PUB_DATE}" \
|
--arg pub_date "${PUB_DATE}" \
|
||||||
--arg notes "${NOTES}" \
|
--arg notes "${NOTES}" \
|
||||||
--arg darwin_sig "$DARWIN_SIG" \
|
--arg darwin_sig "$DARWIN_SIG" \
|
||||||
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
|
--arg darwin_url "$RELEASE_DIR/macos/Zoo%20Modeling%20App.app.tar.gz" \
|
||||||
--arg linux_sig "$LINUX_SIG" \
|
|
||||||
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage.tar.gz" \
|
|
||||||
--arg windows_sig "$WINDOWS_SIG" \
|
--arg windows_sig "$WINDOWS_SIG" \
|
||||||
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
--arg windows_url "$RELEASE_DIR/msi/Zoo%20Modeling%20App_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
||||||
'{
|
'{
|
||||||
"version": $version,
|
"version": $version,
|
||||||
"pub_date": $pub_date,
|
"pub_date": $pub_date,
|
||||||
@ -304,10 +303,6 @@ jobs:
|
|||||||
"signature": $darwin_sig,
|
"signature": $darwin_sig,
|
||||||
"url": $darwin_url
|
"url": $darwin_url
|
||||||
},
|
},
|
||||||
"linux-x86_64": {
|
|
||||||
"signature": $linux_sig,
|
|
||||||
"url": $linux_url
|
|
||||||
},
|
|
||||||
"windows-x86_64": {
|
"windows-x86_64": {
|
||||||
"signature": $windows_sig,
|
"signature": $windows_sig,
|
||||||
"url": $windows_url
|
"url": $windows_url
|
||||||
@ -318,14 +313,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate the download static endpoint
|
- name: Generate the download static endpoint
|
||||||
run: |
|
run: |
|
||||||
RELEASE_DIR=https://${BUCKET_DIR}/${VERSION}
|
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
||||||
jq --null-input \
|
jq --null-input \
|
||||||
--arg version "${VERSION}" \
|
--arg version "${VERSION}" \
|
||||||
--arg pub_date "${PUB_DATE}" \
|
--arg pub_date "${PUB_DATE}" \
|
||||||
--arg notes "${NOTES}" \
|
--arg notes "${NOTES}" \
|
||||||
--arg darwin_url "$RELEASE_DIR/dmg/KittyCAD%20Modeling_${VERSION_NO_V}_universal.dmg" \
|
--arg darwin_url "$RELEASE_DIR/dmg/Zoo%20Modeling%20App_${VERSION_NO_V}_universal.dmg" \
|
||||||
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage" \
|
--arg windows_url "$RELEASE_DIR/msi/Zoo%20Modeling%20App_${VERSION_NO_V}_x64_en-US.msi" \
|
||||||
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi" \
|
|
||||||
'{
|
'{
|
||||||
"version": $version,
|
"version": $version,
|
||||||
"pub_date": $pub_date,
|
"pub_date": $pub_date,
|
||||||
@ -334,9 +328,6 @@ jobs:
|
|||||||
"dmg-universal": {
|
"dmg-universal": {
|
||||||
"url": $darwin_url
|
"url": $darwin_url
|
||||||
},
|
},
|
||||||
"appimage-x86_64": {
|
|
||||||
"url": $linux_url
|
|
||||||
},
|
|
||||||
"msi-x86_64": {
|
"msi-x86_64": {
|
||||||
"url": $windows_url
|
"url": $windows_url
|
||||||
}
|
}
|
||||||
@ -345,31 +336,31 @@ jobs:
|
|||||||
cat last_download.json
|
cat last_download.json
|
||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
- name: Authenticate to Google Cloud
|
||||||
uses: 'google-github-actions/auth@v2.0.0'
|
uses: 'google-github-actions/auth@v2.1.2'
|
||||||
with:
|
with:
|
||||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||||
|
|
||||||
- name: Set up Google Cloud SDK
|
- name: Set up Google Cloud SDK
|
||||||
uses: google-github-actions/setup-gcloud@v2.0.0
|
uses: google-github-actions/setup-gcloud@v2.1.0
|
||||||
with:
|
with:
|
||||||
project_id: kittycadapi
|
project_id: kittycadapi
|
||||||
|
|
||||||
- name: Upload release files to public bucket
|
- name: Upload release files to public bucket
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.0.0
|
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||||
with:
|
with:
|
||||||
path: artifact
|
path: artifact
|
||||||
glob: '*/*itty*'
|
glob: '*/Zoo*'
|
||||||
parent: false
|
parent: false
|
||||||
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
||||||
|
|
||||||
- name: Upload update endpoint to public bucket
|
- name: Upload update endpoint to public bucket
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.0.0
|
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||||
with:
|
with:
|
||||||
path: last_update.json
|
path: last_update.json
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
|
|
||||||
- name: Upload download endpoint to public bucket
|
- name: Upload download endpoint to public bucket
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.0.0
|
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||||
with:
|
with:
|
||||||
path: last_download.json
|
path: last_download.json
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
@ -378,4 +369,29 @@ jobs:
|
|||||||
if: ${{ github.event_name == 'release' }}
|
if: ${{ github.event_name == 'release' }}
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: artifact/*/*itty*
|
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
|
10
.github/workflows/playwright.yml
vendored
@ -4,6 +4,11 @@ on:
|
|||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
playwright-ubuntu:
|
playwright-ubuntu:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
@ -14,7 +19,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- uses: KittyCAD/action-install-cli@v0.2.16
|
- uses: KittyCAD/action-install-cli@v0.2.21
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
@ -79,8 +84,7 @@ jobs:
|
|||||||
|
|
||||||
playwright-macos:
|
playwright-macos:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: macos-latest
|
runs-on: macos-14
|
||||||
needs: playwright-ubuntu
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
|
21
.github/workflows/vector.toml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[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"
|
7
.gitignore
vendored
@ -33,6 +33,7 @@ src/wasm-lib/bindings
|
|||||||
src/wasm-lib/kcl/bindings
|
src/wasm-lib/kcl/bindings
|
||||||
public/wasm_lib_bg.wasm
|
public/wasm_lib_bg.wasm
|
||||||
src/wasm-lib/lcov.info
|
src/wasm-lib/lcov.info
|
||||||
|
src/wasm-lib/grackle/test_json_output
|
||||||
|
|
||||||
e2e/playwright/playwright-secrets.env
|
e2e/playwright/playwright-secrets.env
|
||||||
e2e/playwright/temp1.png
|
e2e/playwright/temp1.png
|
||||||
@ -50,3 +51,9 @@ e2e/playwright/export-snapshots/*embedded.gltf
|
|||||||
/playwright-report/
|
/playwright-report/
|
||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
|
|
||||||
|
|
||||||
|
## generated files
|
||||||
|
src/**/*.typegen.ts
|
||||||
|
|
||||||
|
src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||||
|
@ -10,4 +10,4 @@ src/wasm-lib/kcl/bindings
|
|||||||
e2e/playwright/export-snapshots
|
e2e/playwright/export-snapshots
|
||||||
|
|
||||||
# XState generated files
|
# XState generated files
|
||||||
src/machines/modelingMachine.typegen.ts
|
src/machines/**.typegen.ts
|
||||||
|
10
README.md
@ -94,7 +94,6 @@ For running the rust (not tauri rust though) only, you can
|
|||||||
cd src/wasm-lib
|
cd src/wasm-lib
|
||||||
cargo test
|
cargo test
|
||||||
```
|
```
|
||||||
but you will need to have install ffmpeg prior to.
|
|
||||||
|
|
||||||
## Tauri
|
## Tauri
|
||||||
|
|
||||||
@ -137,7 +136,12 @@ Before you submit a contribution PR to this repo, please ensure that:
|
|||||||
VERSION=x.y.z yarn run bump-jsons
|
VERSION=x.y.z yarn run bump-jsons
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
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 paste in the following
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
console.log(
|
console.log(
|
||||||
@ -183,7 +187,7 @@ For more information on fuzzing you can check out
|
|||||||
First time running plawright locally, you'll need to add the secrets file
|
First time running plawright locally, you'll need to add the secrets file
|
||||||
```bash
|
```bash
|
||||||
touch ./e2e/playwright/playwright-secrets.env
|
touch ./e2e/playwright/playwright-secrets.env
|
||||||
echo 'token="your-token"\nsnapshottoken="your-snapshot-token"' > ./e2e/playwright/playwright-secrets2.env
|
printf 'token="your-token"\nsnapshottoken="your-snapshot-token"' > ./e2e/playwright/playwright-secrets.env
|
||||||
```
|
```
|
||||||
then replace "your-token" with a dev token from dev.zoo.dev/account/api-tokens
|
then replace "your-token" with a dev token from dev.zoo.dev/account/api-tokens
|
||||||
|
|
||||||
|
BIN
app-icon.png
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 120 KiB |
14
docs/kcl/KNOWN-ISSUES.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Known Issues
|
||||||
|
|
||||||
|
The following are bugs that are not in modeling-app or kcl itself. These bugs
|
||||||
|
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.
|
||||||
|
|
||||||
|
- **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.
|
45158
docs/kcl/std.json
7822
docs/kcl/std.md
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 193 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 193 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 259 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 220 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 220 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 220 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 193 KiB |
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('kittycad.io export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('dump.step', '1970-01-01T00:00:00.0+00:00', ('Author unknown'), ('Organization unknown'), 'kittycad.io beta', 'kittycad.io', 'Authorization unknown');
|
FILE_NAME('dump.step', '1970-01-01T00:00:00.0+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
@ -19,59 +19,59 @@ DATA;
|
|||||||
);
|
);
|
||||||
#4 = CARTESIAN_POINT('NONE', (0, 0, -0));
|
#4 = CARTESIAN_POINT('NONE', (0, 0, -0));
|
||||||
#5 = VERTEX_POINT('NONE', #4);
|
#5 = VERTEX_POINT('NONE', #4);
|
||||||
#6 = CARTESIAN_POINT('NONE', (0, -0.0254, -0));
|
#6 = CARTESIAN_POINT('NONE', (0, -0.64516, -0));
|
||||||
#7 = VERTEX_POINT('NONE', #6);
|
#7 = VERTEX_POINT('NONE', #6);
|
||||||
#8 = CARTESIAN_POINT('NONE', (0, -0.0254, 0.1016));
|
#8 = CARTESIAN_POINT('NONE', (0, -0.64516, 2.58064));
|
||||||
#9 = VERTEX_POINT('NONE', #8);
|
#9 = VERTEX_POINT('NONE', #8);
|
||||||
#10 = CARTESIAN_POINT('NONE', (0, 0, 0.1016));
|
#10 = CARTESIAN_POINT('NONE', (0, 0, 2.58064));
|
||||||
#11 = VERTEX_POINT('NONE', #10);
|
#11 = VERTEX_POINT('NONE', #10);
|
||||||
#12 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0));
|
#12 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0));
|
||||||
#13 = VERTEX_POINT('NONE', #12);
|
#13 = VERTEX_POINT('NONE', #12);
|
||||||
#14 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, 0.1016));
|
#14 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, 2.58064));
|
||||||
#15 = VERTEX_POINT('NONE', #14);
|
#15 = VERTEX_POINT('NONE', #14);
|
||||||
#16 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0));
|
#16 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0));
|
||||||
#17 = VERTEX_POINT('NONE', #16);
|
#17 = VERTEX_POINT('NONE', #16);
|
||||||
#18 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, 0.1016));
|
#18 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, 2.58064));
|
||||||
#19 = VERTEX_POINT('NONE', #18);
|
#19 = VERTEX_POINT('NONE', #18);
|
||||||
#20 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0));
|
#20 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0));
|
||||||
#21 = VERTEX_POINT('NONE', #20);
|
#21 = VERTEX_POINT('NONE', #20);
|
||||||
#22 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, 0.1016));
|
#22 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, 2.58064));
|
||||||
#23 = VERTEX_POINT('NONE', #22);
|
#23 = VERTEX_POINT('NONE', #22);
|
||||||
#24 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0));
|
#24 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0));
|
||||||
#25 = VERTEX_POINT('NONE', #24);
|
#25 = VERTEX_POINT('NONE', #24);
|
||||||
#26 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, 0.1016));
|
#26 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, 2.58064));
|
||||||
#27 = VERTEX_POINT('NONE', #26);
|
#27 = VERTEX_POINT('NONE', #26);
|
||||||
#28 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0));
|
#28 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0));
|
||||||
#29 = VERTEX_POINT('NONE', #28);
|
#29 = VERTEX_POINT('NONE', #28);
|
||||||
#30 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, 0.1016));
|
#30 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, 2.58064));
|
||||||
#31 = VERTEX_POINT('NONE', #30);
|
#31 = VERTEX_POINT('NONE', #30);
|
||||||
#32 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0));
|
#32 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0));
|
||||||
#33 = VERTEX_POINT('NONE', #32);
|
#33 = VERTEX_POINT('NONE', #32);
|
||||||
#34 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, 0.1016));
|
#34 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, 2.58064));
|
||||||
#35 = VERTEX_POINT('NONE', #34);
|
#35 = VERTEX_POINT('NONE', #34);
|
||||||
#36 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0));
|
#36 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0));
|
||||||
#37 = VERTEX_POINT('NONE', #36);
|
#37 = VERTEX_POINT('NONE', #36);
|
||||||
#38 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, 0.1016));
|
#38 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, 2.58064));
|
||||||
#39 = VERTEX_POINT('NONE', #38);
|
#39 = VERTEX_POINT('NONE', #38);
|
||||||
#40 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0));
|
#40 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0));
|
||||||
#41 = VERTEX_POINT('NONE', #40);
|
#41 = VERTEX_POINT('NONE', #40);
|
||||||
#42 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, 0.1016));
|
#42 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, 2.58064));
|
||||||
#43 = VERTEX_POINT('NONE', #42);
|
#43 = VERTEX_POINT('NONE', #42);
|
||||||
#44 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0));
|
#44 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0));
|
||||||
#45 = VERTEX_POINT('NONE', #44);
|
#45 = VERTEX_POINT('NONE', #44);
|
||||||
#46 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, 0.1016));
|
#46 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, 2.58064));
|
||||||
#47 = VERTEX_POINT('NONE', #46);
|
#47 = VERTEX_POINT('NONE', #46);
|
||||||
#48 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0));
|
#48 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0));
|
||||||
#49 = VERTEX_POINT('NONE', #48);
|
#49 = VERTEX_POINT('NONE', #48);
|
||||||
#50 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, 0.1016));
|
#50 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, 2.58064));
|
||||||
#51 = VERTEX_POINT('NONE', #50);
|
#51 = VERTEX_POINT('NONE', #50);
|
||||||
#52 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0));
|
#52 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0));
|
||||||
#53 = VERTEX_POINT('NONE', #52);
|
#53 = VERTEX_POINT('NONE', #52);
|
||||||
#54 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, 0.1016));
|
#54 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, 2.58064));
|
||||||
#55 = VERTEX_POINT('NONE', #54);
|
#55 = VERTEX_POINT('NONE', #54);
|
||||||
#56 = CARTESIAN_POINT('NONE', (0, 0.0254, -0));
|
#56 = CARTESIAN_POINT('NONE', (0, 0.64516, -0));
|
||||||
#57 = VERTEX_POINT('NONE', #56);
|
#57 = VERTEX_POINT('NONE', #56);
|
||||||
#58 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016));
|
#58 = CARTESIAN_POINT('NONE', (0, 0.64516, 2.58064));
|
||||||
#59 = VERTEX_POINT('NONE', #58);
|
#59 = VERTEX_POINT('NONE', #58);
|
||||||
#60 = DIRECTION('NONE', (0, -1, 0));
|
#60 = DIRECTION('NONE', (0, -1, 0));
|
||||||
#61 = VECTOR('NONE', #60, 1);
|
#61 = VECTOR('NONE', #60, 1);
|
||||||
@ -79,11 +79,11 @@ DATA;
|
|||||||
#63 = LINE('NONE', #62, #61);
|
#63 = LINE('NONE', #62, #61);
|
||||||
#64 = DIRECTION('NONE', (0, 0, 1));
|
#64 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#65 = VECTOR('NONE', #64, 1);
|
#65 = VECTOR('NONE', #64, 1);
|
||||||
#66 = CARTESIAN_POINT('NONE', (0, -0.0254, -0));
|
#66 = CARTESIAN_POINT('NONE', (0, -0.64516, -0));
|
||||||
#67 = LINE('NONE', #66, #65);
|
#67 = LINE('NONE', #66, #65);
|
||||||
#68 = DIRECTION('NONE', (0, -1, 0));
|
#68 = DIRECTION('NONE', (0, -1, 0));
|
||||||
#69 = VECTOR('NONE', #68, 1);
|
#69 = VECTOR('NONE', #68, 1);
|
||||||
#70 = CARTESIAN_POINT('NONE', (0, 0, 0.1016));
|
#70 = CARTESIAN_POINT('NONE', (0, 0, 2.58064));
|
||||||
#71 = LINE('NONE', #70, #69);
|
#71 = LINE('NONE', #70, #69);
|
||||||
#72 = DIRECTION('NONE', (0, 0, 1));
|
#72 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#73 = VECTOR('NONE', #72, 1);
|
#73 = VECTOR('NONE', #72, 1);
|
||||||
@ -91,155 +91,155 @@ DATA;
|
|||||||
#75 = LINE('NONE', #74, #73);
|
#75 = LINE('NONE', #74, #73);
|
||||||
#76 = DIRECTION('NONE', (1, 0, 0));
|
#76 = DIRECTION('NONE', (1, 0, 0));
|
||||||
#77 = VECTOR('NONE', #76, 1);
|
#77 = VECTOR('NONE', #76, 1);
|
||||||
#78 = CARTESIAN_POINT('NONE', (0, -0.0254, -0));
|
#78 = CARTESIAN_POINT('NONE', (0, -0.64516, -0));
|
||||||
#79 = LINE('NONE', #78, #77);
|
#79 = LINE('NONE', #78, #77);
|
||||||
#80 = DIRECTION('NONE', (0, 0, 1));
|
#80 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#81 = VECTOR('NONE', #80, 1);
|
#81 = VECTOR('NONE', #80, 1);
|
||||||
#82 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0));
|
#82 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0));
|
||||||
#83 = LINE('NONE', #82, #81);
|
#83 = LINE('NONE', #82, #81);
|
||||||
#84 = DIRECTION('NONE', (1, 0, 0));
|
#84 = DIRECTION('NONE', (1, 0, 0));
|
||||||
#85 = VECTOR('NONE', #84, 1);
|
#85 = VECTOR('NONE', #84, 1);
|
||||||
#86 = CARTESIAN_POINT('NONE', (0, -0.0254, 0.1016));
|
#86 = CARTESIAN_POINT('NONE', (0, -0.64516, 2.58064));
|
||||||
#87 = LINE('NONE', #86, #85);
|
#87 = LINE('NONE', #86, #85);
|
||||||
#88 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0));
|
#88 = DIRECTION('NONE', (0.819152044288992, -0.5735764363510459, 0));
|
||||||
#89 = VECTOR('NONE', #88, 1);
|
#89 = VECTOR('NONE', #88, 1);
|
||||||
#90 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0));
|
#90 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0));
|
||||||
#91 = LINE('NONE', #90, #89);
|
#91 = LINE('NONE', #90, #89);
|
||||||
#92 = DIRECTION('NONE', (0, 0, 1));
|
#92 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#93 = VECTOR('NONE', #92, 1);
|
#93 = VECTOR('NONE', #92, 1);
|
||||||
#94 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0));
|
#94 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0));
|
||||||
#95 = LINE('NONE', #94, #93);
|
#95 = LINE('NONE', #94, #93);
|
||||||
#96 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0));
|
#96 = DIRECTION('NONE', (0.819152044288992, -0.5735764363510459, 0));
|
||||||
#97 = VECTOR('NONE', #96, 1);
|
#97 = VECTOR('NONE', #96, 1);
|
||||||
#98 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, 0.1016));
|
#98 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, 2.58064));
|
||||||
#99 = LINE('NONE', #98, #97);
|
#99 = LINE('NONE', #98, #97);
|
||||||
#100 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0));
|
#100 = DIRECTION('NONE', (1, -0.00000000000000038794063361359933, 0));
|
||||||
#101 = VECTOR('NONE', #100, 1);
|
#101 = VECTOR('NONE', #100, 1);
|
||||||
#102 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0));
|
#102 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0));
|
||||||
#103 = LINE('NONE', #102, #101);
|
#103 = LINE('NONE', #102, #101);
|
||||||
#104 = DIRECTION('NONE', (0, 0, 1));
|
#104 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#105 = VECTOR('NONE', #104, 1);
|
#105 = VECTOR('NONE', #104, 1);
|
||||||
#106 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0));
|
#106 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0));
|
||||||
#107 = LINE('NONE', #106, #105);
|
#107 = LINE('NONE', #106, #105);
|
||||||
#108 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0));
|
#108 = DIRECTION('NONE', (1, -0.00000000000000038794063361359933, 0));
|
||||||
#109 = VECTOR('NONE', #108, 1);
|
#109 = VECTOR('NONE', #108, 1);
|
||||||
#110 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, 0.1016));
|
#110 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, 2.58064));
|
||||||
#111 = LINE('NONE', #110, #109);
|
#111 = LINE('NONE', #110, #109);
|
||||||
#112 = DIRECTION('NONE', (0, 1, 0));
|
#112 = DIRECTION('NONE', (0, 1, 0));
|
||||||
#113 = VECTOR('NONE', #112, 1);
|
#113 = VECTOR('NONE', #112, 1);
|
||||||
#114 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0));
|
#114 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0));
|
||||||
#115 = LINE('NONE', #114, #113);
|
#115 = LINE('NONE', #114, #113);
|
||||||
#116 = DIRECTION('NONE', (0, 0, 1));
|
#116 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#117 = VECTOR('NONE', #116, 1);
|
#117 = VECTOR('NONE', #116, 1);
|
||||||
#118 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0));
|
#118 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0));
|
||||||
#119 = LINE('NONE', #118, #117);
|
#119 = LINE('NONE', #118, #117);
|
||||||
#120 = DIRECTION('NONE', (0, 1, 0));
|
#120 = DIRECTION('NONE', (0, 1, 0));
|
||||||
#121 = VECTOR('NONE', #120, 1);
|
#121 = VECTOR('NONE', #120, 1);
|
||||||
#122 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, 0.1016));
|
#122 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, 2.58064));
|
||||||
#123 = LINE('NONE', #122, #121);
|
#123 = LINE('NONE', #122, #121);
|
||||||
#124 = DIRECTION('NONE', (-1, 0, 0));
|
#124 = DIRECTION('NONE', (-1, 0, 0));
|
||||||
#125 = VECTOR('NONE', #124, 1);
|
#125 = VECTOR('NONE', #124, 1);
|
||||||
#126 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0));
|
#126 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0));
|
||||||
#127 = LINE('NONE', #126, #125);
|
#127 = LINE('NONE', #126, #125);
|
||||||
#128 = DIRECTION('NONE', (0, 0, 1));
|
#128 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#129 = VECTOR('NONE', #128, 1);
|
#129 = VECTOR('NONE', #128, 1);
|
||||||
#130 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0));
|
#130 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0));
|
||||||
#131 = LINE('NONE', #130, #129);
|
#131 = LINE('NONE', #130, #129);
|
||||||
#132 = DIRECTION('NONE', (-1, 0, 0));
|
#132 = DIRECTION('NONE', (-1, 0, 0));
|
||||||
#133 = VECTOR('NONE', #132, 1);
|
#133 = VECTOR('NONE', #132, 1);
|
||||||
#134 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, 0.1016));
|
#134 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, 2.58064));
|
||||||
#135 = LINE('NONE', #134, #133);
|
#135 = LINE('NONE', #134, #133);
|
||||||
#136 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
|
#136 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
|
||||||
#137 = VECTOR('NONE', #136, 1);
|
#137 = VECTOR('NONE', #136, 1);
|
||||||
#138 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0));
|
#138 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0));
|
||||||
#139 = LINE('NONE', #138, #137);
|
#139 = LINE('NONE', #138, #137);
|
||||||
#140 = DIRECTION('NONE', (0, 0, 1));
|
#140 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#141 = VECTOR('NONE', #140, 1);
|
#141 = VECTOR('NONE', #140, 1);
|
||||||
#142 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0));
|
#142 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0));
|
||||||
#143 = LINE('NONE', #142, #141);
|
#143 = LINE('NONE', #142, #141);
|
||||||
#144 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
|
#144 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
|
||||||
#145 = VECTOR('NONE', #144, 1);
|
#145 = VECTOR('NONE', #144, 1);
|
||||||
#146 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, 0.1016));
|
#146 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, 2.58064));
|
||||||
#147 = LINE('NONE', #146, #145);
|
#147 = LINE('NONE', #146, #145);
|
||||||
#148 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0));
|
#148 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406992, 0));
|
||||||
#149 = VECTOR('NONE', #148, 1);
|
#149 = VECTOR('NONE', #148, 1);
|
||||||
#150 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0));
|
#150 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0));
|
||||||
#151 = LINE('NONE', #150, #149);
|
#151 = LINE('NONE', #150, #149);
|
||||||
#152 = DIRECTION('NONE', (0, 0, 1));
|
#152 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#153 = VECTOR('NONE', #152, 1);
|
#153 = VECTOR('NONE', #152, 1);
|
||||||
#154 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0));
|
#154 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0));
|
||||||
#155 = LINE('NONE', #154, #153);
|
#155 = LINE('NONE', #154, #153);
|
||||||
#156 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0));
|
#156 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406992, 0));
|
||||||
#157 = VECTOR('NONE', #156, 1);
|
#157 = VECTOR('NONE', #156, 1);
|
||||||
#158 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, 0.1016));
|
#158 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, 2.58064));
|
||||||
#159 = LINE('NONE', #158, #157);
|
#159 = LINE('NONE', #158, #157);
|
||||||
#160 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0));
|
#160 = DIRECTION('NONE', (1, -0.0000000000000001378647737807002, 0));
|
||||||
#161 = VECTOR('NONE', #160, 1);
|
#161 = VECTOR('NONE', #160, 1);
|
||||||
#162 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0));
|
#162 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0));
|
||||||
#163 = LINE('NONE', #162, #161);
|
#163 = LINE('NONE', #162, #161);
|
||||||
#164 = DIRECTION('NONE', (0, 0, 1));
|
#164 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#165 = VECTOR('NONE', #164, 1);
|
#165 = VECTOR('NONE', #164, 1);
|
||||||
#166 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0));
|
#166 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0));
|
||||||
#167 = LINE('NONE', #166, #165);
|
#167 = LINE('NONE', #166, #165);
|
||||||
#168 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0));
|
#168 = DIRECTION('NONE', (1, -0.0000000000000001378647737807002, 0));
|
||||||
#169 = VECTOR('NONE', #168, 1);
|
#169 = VECTOR('NONE', #168, 1);
|
||||||
#170 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, 0.1016));
|
#170 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, 2.58064));
|
||||||
#171 = LINE('NONE', #170, #169);
|
#171 = LINE('NONE', #170, #169);
|
||||||
#172 = DIRECTION('NONE', (0, 1, 0));
|
#172 = DIRECTION('NONE', (0, 1, 0));
|
||||||
#173 = VECTOR('NONE', #172, 1);
|
#173 = VECTOR('NONE', #172, 1);
|
||||||
#174 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0));
|
#174 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0));
|
||||||
#175 = LINE('NONE', #174, #173);
|
#175 = LINE('NONE', #174, #173);
|
||||||
#176 = DIRECTION('NONE', (0, 0, 1));
|
#176 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#177 = VECTOR('NONE', #176, 1);
|
#177 = VECTOR('NONE', #176, 1);
|
||||||
#178 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0));
|
#178 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0));
|
||||||
#179 = LINE('NONE', #178, #177);
|
#179 = LINE('NONE', #178, #177);
|
||||||
#180 = DIRECTION('NONE', (0, 1, 0));
|
#180 = DIRECTION('NONE', (0, 1, 0));
|
||||||
#181 = VECTOR('NONE', #180, 1);
|
#181 = VECTOR('NONE', #180, 1);
|
||||||
#182 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, 0.1016));
|
#182 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, 2.58064));
|
||||||
#183 = LINE('NONE', #182, #181);
|
#183 = LINE('NONE', #182, #181);
|
||||||
#184 = DIRECTION('NONE', (-1, 0, 0));
|
#184 = DIRECTION('NONE', (-1, 0, 0));
|
||||||
#185 = VECTOR('NONE', #184, 1);
|
#185 = VECTOR('NONE', #184, 1);
|
||||||
#186 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0));
|
#186 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0));
|
||||||
#187 = LINE('NONE', #186, #185);
|
#187 = LINE('NONE', #186, #185);
|
||||||
#188 = DIRECTION('NONE', (0, 0, 1));
|
#188 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#189 = VECTOR('NONE', #188, 1);
|
#189 = VECTOR('NONE', #188, 1);
|
||||||
#190 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0));
|
#190 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0));
|
||||||
#191 = LINE('NONE', #190, #189);
|
#191 = LINE('NONE', #190, #189);
|
||||||
#192 = DIRECTION('NONE', (-1, 0, 0));
|
#192 = DIRECTION('NONE', (-1, 0, 0));
|
||||||
#193 = VECTOR('NONE', #192, 1);
|
#193 = VECTOR('NONE', #192, 1);
|
||||||
#194 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, 0.1016));
|
#194 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, 2.58064));
|
||||||
#195 = LINE('NONE', #194, #193);
|
#195 = LINE('NONE', #194, #193);
|
||||||
#196 = DIRECTION('NONE', (-0.90630778703665, -0.42261826174069944, 0));
|
#196 = DIRECTION('NONE', (-0.90630778703665, -0.4226182617406995, 0));
|
||||||
#197 = VECTOR('NONE', #196, 1);
|
#197 = VECTOR('NONE', #196, 1);
|
||||||
#198 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0));
|
#198 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0));
|
||||||
#199 = LINE('NONE', #198, #197);
|
#199 = LINE('NONE', #198, #197);
|
||||||
#200 = DIRECTION('NONE', (0, 0, 1));
|
#200 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#201 = VECTOR('NONE', #200, 1);
|
#201 = VECTOR('NONE', #200, 1);
|
||||||
#202 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0));
|
#202 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0));
|
||||||
#203 = LINE('NONE', #202, #201);
|
#203 = LINE('NONE', #202, #201);
|
||||||
#204 = DIRECTION('NONE', (-0.90630778703665, -0.42261826174069944, 0));
|
#204 = DIRECTION('NONE', (-0.90630778703665, -0.4226182617406995, 0));
|
||||||
#205 = VECTOR('NONE', #204, 1);
|
#205 = VECTOR('NONE', #204, 1);
|
||||||
#206 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, 0.1016));
|
#206 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, 2.58064));
|
||||||
#207 = LINE('NONE', #206, #205);
|
#207 = LINE('NONE', #206, #205);
|
||||||
#208 = DIRECTION('NONE', (-1, 0, 0));
|
#208 = DIRECTION('NONE', (-1, 0, 0));
|
||||||
#209 = VECTOR('NONE', #208, 1);
|
#209 = VECTOR('NONE', #208, 1);
|
||||||
#210 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0));
|
#210 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0));
|
||||||
#211 = LINE('NONE', #210, #209);
|
#211 = LINE('NONE', #210, #209);
|
||||||
#212 = DIRECTION('NONE', (0, 0, 1));
|
#212 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#213 = VECTOR('NONE', #212, 1);
|
#213 = VECTOR('NONE', #212, 1);
|
||||||
#214 = CARTESIAN_POINT('NONE', (0, 0.0254, -0));
|
#214 = CARTESIAN_POINT('NONE', (0, 0.64516, -0));
|
||||||
#215 = LINE('NONE', #214, #213);
|
#215 = LINE('NONE', #214, #213);
|
||||||
#216 = DIRECTION('NONE', (-1, 0, 0));
|
#216 = DIRECTION('NONE', (-1, 0, 0));
|
||||||
#217 = VECTOR('NONE', #216, 1);
|
#217 = VECTOR('NONE', #216, 1);
|
||||||
#218 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, 0.1016));
|
#218 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, 2.58064));
|
||||||
#219 = LINE('NONE', #218, #217);
|
#219 = LINE('NONE', #218, #217);
|
||||||
#220 = DIRECTION('NONE', (0, -1, 0));
|
#220 = DIRECTION('NONE', (0, -1, 0));
|
||||||
#221 = VECTOR('NONE', #220, 1);
|
#221 = VECTOR('NONE', #220, 1);
|
||||||
#222 = CARTESIAN_POINT('NONE', (0, 0.0254, -0));
|
#222 = CARTESIAN_POINT('NONE', (0, 0.64516, -0));
|
||||||
#223 = LINE('NONE', #222, #221);
|
#223 = LINE('NONE', #222, #221);
|
||||||
#224 = DIRECTION('NONE', (0, -1, 0));
|
#224 = DIRECTION('NONE', (0, -1, 0));
|
||||||
#225 = VECTOR('NONE', #224, 1);
|
#225 = VECTOR('NONE', #224, 1);
|
||||||
#226 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016));
|
#226 = CARTESIAN_POINT('NONE', (0, 0.64516, 2.58064));
|
||||||
#227 = LINE('NONE', #226, #225);
|
#227 = LINE('NONE', #226, #225);
|
||||||
#228 = EDGE_CURVE('NONE', #5, #7, #63, .T.);
|
#228 = EDGE_CURVE('NONE', #5, #7, #63, .T.);
|
||||||
#229 = EDGE_CURVE('NONE', #7, #9, #67, .T.);
|
#229 = EDGE_CURVE('NONE', #7, #9, #67, .T.);
|
||||||
@ -383,67 +383,67 @@ DATA;
|
|||||||
#367 = ORIENTED_EDGE('NONE', *, *, #267, .T.);
|
#367 = ORIENTED_EDGE('NONE', *, *, #267, .T.);
|
||||||
#368 = ORIENTED_EDGE('NONE', *, *, #269, .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));
|
#369 = EDGE_LOOP('NONE', (#355, #356, #357, #358, #359, #360, #361, #362, #363, #364, #365, #366, #367, #368));
|
||||||
#370 = CARTESIAN_POINT('NONE', (0, -0.0127, 0.0508));
|
#370 = CARTESIAN_POINT('NONE', (0, -0.3225799999999985, 1.2903199999999995));
|
||||||
#371 = DIRECTION('NONE', (-1, 0, -0));
|
#371 = DIRECTION('NONE', (-1, -0, 0));
|
||||||
#372 = AXIS2_PLACEMENT_3D('NONE', #370, #371, $);
|
#372 = AXIS2_PLACEMENT_3D('NONE', #370, #371, $);
|
||||||
#373 = PLANE('NONE', #372);
|
#373 = PLANE('NONE', #372);
|
||||||
#374 = CARTESIAN_POINT('NONE', (0.039306734695977924, -0.025399999999999995, 0.0508));
|
#374 = CARTESIAN_POINT('NONE', (0.9983910612778368, -0.6451599999999998, 1.2903199999999997));
|
||||||
#375 = DIRECTION('NONE', (0, -1, -0));
|
#375 = DIRECTION('NONE', (0, -1, 0));
|
||||||
#376 = AXIS2_PLACEMENT_3D('NONE', #374, #375, $);
|
#376 = AXIS2_PLACEMENT_3D('NONE', #374, #375, $);
|
||||||
#377 = PLANE('NONE', #376);
|
#377 = PLANE('NONE', #376);
|
||||||
#378 = CARTESIAN_POINT('NONE', (0.11488842876320533, -0.05079999999999996, 0.05079999999999999));
|
#378 = CARTESIAN_POINT('NONE', (2.918166090585415, -1.2903199999999988, 1.2903199999999997));
|
||||||
#379 = DIRECTION('NONE', (-0.5735764363510459, -0.819152044288992, 0));
|
#379 = DIRECTION('NONE', (-0.5735764363510459, -0.8191520442889919, 0));
|
||||||
#380 = AXIS2_PLACEMENT_3D('NONE', #378, #379, $);
|
#380 = AXIS2_PLACEMENT_3D('NONE', #378, #379, $);
|
||||||
#381 = PLANE('NONE', #380);
|
#381 = PLANE('NONE', #380);
|
||||||
#382 = CARTESIAN_POINT('NONE', (0.19623169406722757, -0.07619999999999999, 0.0508));
|
#382 = CARTESIAN_POINT('NONE', (4.984285029307579, -1.9354799999999992, 1.2903199999999997));
|
||||||
#383 = DIRECTION('NONE', (0, -1, -0));
|
#383 = DIRECTION('NONE', (0, -1, 0));
|
||||||
#384 = AXIS2_PLACEMENT_3D('NONE', #382, #383, $);
|
#384 = AXIS2_PLACEMENT_3D('NONE', #382, #383, $);
|
||||||
#385 = PLANE('NONE', #384);
|
#385 = PLANE('NONE', #384);
|
||||||
#386 = CARTESIAN_POINT('NONE', (0.2413, -0.06985, 0.0508));
|
#386 = CARTESIAN_POINT('NONE', (6.129019999999999, -1.7741899999999997, 1.2903199999999997));
|
||||||
#387 = DIRECTION('NONE', (1, 0, -0));
|
#387 = DIRECTION('NONE', (1, -0, 0));
|
||||||
#388 = AXIS2_PLACEMENT_3D('NONE', #386, #387, $);
|
#388 = AXIS2_PLACEMENT_3D('NONE', #386, #387, $);
|
||||||
#389 = PLANE('NONE', #388);
|
#389 = PLANE('NONE', #388);
|
||||||
#390 = CARTESIAN_POINT('NONE', (0.19823384137660915, -0.0635, 0.0508));
|
#390 = CARTESIAN_POINT('NONE', (5.035139570965871, -1.6128999999999998, 1.2903199999999997));
|
||||||
#391 = DIRECTION('NONE', (0, 1, -0));
|
#391 = DIRECTION('NONE', (0, 1, -0));
|
||||||
#392 = AXIS2_PLACEMENT_3D('NONE', #390, #391, $);
|
#392 = AXIS2_PLACEMENT_3D('NONE', #390, #391, $);
|
||||||
#393 = PLANE('NONE', #392);
|
#393 = PLANE('NONE', #392);
|
||||||
#394 = CARTESIAN_POINT('NONE', (0.10982398353915601, -0.03174999999999997, 0.0508));
|
#394 = CARTESIAN_POINT('NONE', (2.7895291818945633, -0.8064499999999998, 1.2903199999999995));
|
||||||
#395 = DIRECTION('NONE', (0.573576436351046, 0.8191520442889918, -0));
|
#395 = DIRECTION('NONE', (0.5735764363510459, 0.8191520442889918, -0));
|
||||||
#396 = AXIS2_PLACEMENT_3D('NONE', #394, #395, $);
|
#396 = AXIS2_PLACEMENT_3D('NONE', #394, #395, $);
|
||||||
#397 = PLANE('NONE', #396);
|
#397 = PLANE('NONE', #396);
|
||||||
#398 = CARTESIAN_POINT('NONE', (0.105333141160801, 0.019049999999999987, 0.0508));
|
#398 = CARTESIAN_POINT('NONE', (2.6754617854843468, 0.4838700000000003, 1.2903199999999997));
|
||||||
#399 = DIRECTION('NONE', (0.4226182617406993, -0.90630778703665, -0));
|
#399 = DIRECTION('NONE', (0.4226182617406992, -0.90630778703665, 0));
|
||||||
#400 = AXIS2_PLACEMENT_3D('NONE', #398, #399, $);
|
#400 = AXIS2_PLACEMENT_3D('NONE', #398, #399, $);
|
||||||
#401 = PLANE('NONE', #400);
|
#401 = PLANE('NONE', #400);
|
||||||
#402 = CARTESIAN_POINT('NONE', (0.19374299899825406, 0.0381, 0.0508));
|
#402 = CARTESIAN_POINT('NONE', (4.921072174555653, 0.9677399999999998, 1.2903199999999995));
|
||||||
#403 = DIRECTION('NONE', (0, -1, -0));
|
#403 = DIRECTION('NONE', (0, -1, 0));
|
||||||
#404 = AXIS2_PLACEMENT_3D('NONE', #402, #403, $);
|
#404 = AXIS2_PLACEMENT_3D('NONE', #402, #403, $);
|
||||||
#405 = PLANE('NONE', #404);
|
#405 = PLANE('NONE', #404);
|
||||||
#406 = CARTESIAN_POINT('NONE', (0.2413, 0.044449999999999996, 0.0508));
|
#406 = CARTESIAN_POINT('NONE', (6.129019999999998, 1.1290299999999989, 1.2903199999999995));
|
||||||
#407 = DIRECTION('NONE', (1, 0, -0));
|
#407 = DIRECTION('NONE', (1, -0, 0));
|
||||||
#408 = AXIS2_PLACEMENT_3D('NONE', #406, #407, $);
|
#408 = AXIS2_PLACEMENT_3D('NONE', #406, #407, $);
|
||||||
#409 = PLANE('NONE', #408);
|
#409 = PLANE('NONE', #408);
|
||||||
#410 = CARTESIAN_POINT('NONE', (0.19233523789047138, 0.0508, 0.0508));
|
#410 = CARTESIAN_POINT('NONE', (4.8853150424179725, 1.2903199999999997, 1.2903199999999997));
|
||||||
#411 = DIRECTION('NONE', (0, 1, -0));
|
#411 = DIRECTION('NONE', (0, 1, -0));
|
||||||
#412 = AXIS2_PLACEMENT_3D('NONE', #410, #411, $);
|
#412 = AXIS2_PLACEMENT_3D('NONE', #410, #411, $);
|
||||||
#413 = PLANE('NONE', #412);
|
#413 = PLANE('NONE', #412);
|
||||||
#414 = CARTESIAN_POINT('NONE', (0.11613523789047137, 0.0381, 0.05079999999999999));
|
#414 = CARTESIAN_POINT('NONE', (2.9498350424179733, 0.9677399999999998, 1.2903199999999997));
|
||||||
#415 = DIRECTION('NONE', (-0.42261826174069966, 0.90630778703665, -0));
|
#415 = DIRECTION('NONE', (-0.42261826174069933, 0.9063077870366499, -0));
|
||||||
#416 = AXIS2_PLACEMENT_3D('NONE', #414, #415, $);
|
#416 = AXIS2_PLACEMENT_3D('NONE', #414, #415, $);
|
||||||
#417 = PLANE('NONE', #416);
|
#417 = PLANE('NONE', #416);
|
||||||
#418 = CARTESIAN_POINT('NONE', (0.044449999999999996, 0.0254, 0.0508));
|
#418 = CARTESIAN_POINT('NONE', (1.1290299999999998, 0.6451599999999998, 1.29032));
|
||||||
#419 = DIRECTION('NONE', (0, 1, -0));
|
#419 = DIRECTION('NONE', (0, 1, -0));
|
||||||
#420 = AXIS2_PLACEMENT_3D('NONE', #418, #419, $);
|
#420 = AXIS2_PLACEMENT_3D('NONE', #418, #419, $);
|
||||||
#421 = PLANE('NONE', #420);
|
#421 = PLANE('NONE', #420);
|
||||||
#422 = CARTESIAN_POINT('NONE', (0, 0.0127, 0.0508));
|
#422 = CARTESIAN_POINT('NONE', (0, 0.32257999999999987, 1.2903199999999995));
|
||||||
#423 = DIRECTION('NONE', (-1, 0, -0));
|
#423 = DIRECTION('NONE', (-1, -0, 0));
|
||||||
#424 = AXIS2_PLACEMENT_3D('NONE', #422, #423, $);
|
#424 = AXIS2_PLACEMENT_3D('NONE', #422, #423, $);
|
||||||
#425 = PLANE('NONE', #424);
|
#425 = PLANE('NONE', #424);
|
||||||
#426 = CARTESIAN_POINT('NONE', (0, 0, -0));
|
#426 = CARTESIAN_POINT('NONE', (0, 0, -0));
|
||||||
#427 = DIRECTION('NONE', (0, 0, 1));
|
#427 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#428 = AXIS2_PLACEMENT_3D('NONE', #426, #427, $);
|
#428 = AXIS2_PLACEMENT_3D('NONE', #426, #427, $);
|
||||||
#429 = PLANE('NONE', #428);
|
#429 = PLANE('NONE', #428);
|
||||||
#430 = CARTESIAN_POINT('NONE', (0, 0, 0.1016));
|
#430 = CARTESIAN_POINT('NONE', (0, 0, 2.58064));
|
||||||
#431 = DIRECTION('NONE', (0, 0, 1));
|
#431 = DIRECTION('NONE', (0, 0, 1));
|
||||||
#432 = AXIS2_PLACEMENT_3D('NONE', #430, #431, $);
|
#432 = AXIS2_PLACEMENT_3D('NONE', #430, #431, $);
|
||||||
#433 = PLANE('NONE', #432);
|
#433 = PLANE('NONE', #432);
|
||||||
@ -475,7 +475,7 @@ DATA;
|
|||||||
#459 = ADVANCED_FACE('NONE', (#458), #421, .T.);
|
#459 = ADVANCED_FACE('NONE', (#458), #421, .T.);
|
||||||
#460 = FACE_OUTER_BOUND('NONE', #339, .T.);
|
#460 = FACE_OUTER_BOUND('NONE', #339, .T.);
|
||||||
#461 = ADVANCED_FACE('NONE', (#460), #425, .T.);
|
#461 = ADVANCED_FACE('NONE', (#460), #425, .T.);
|
||||||
#462 = FACE_OUTER_BOUND('NONE', #354, .T.);
|
#462 = FACE_OUTER_BOUND('NONE', #354, .F.);
|
||||||
#463 = ADVANCED_FACE('NONE', (#462), #429, .F.);
|
#463 = ADVANCED_FACE('NONE', (#462), #429, .F.);
|
||||||
#464 = FACE_OUTER_BOUND('NONE', #369, .T.);
|
#464 = FACE_OUTER_BOUND('NONE', #369, .T.);
|
||||||
#465 = ADVANCED_FACE('NONE', (#464), #433, .T.);
|
#465 = ADVANCED_FACE('NONE', (#464), #433, .T.);
|
||||||
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 221 KiB |
@ -1,478 +1,478 @@
|
|||||||
solid unnamed
|
solid unnamed
|
||||||
facet normal -1 0 0
|
facet normal -1 0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 -4 0
|
vertex 0 -101.600006 0
|
||||||
vertex 0 -0 0
|
vertex 0 -0 0
|
||||||
vertex 0 -4 -1
|
vertex 0 -101.600006 -25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -1 0 0
|
facet normal -1 0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 -4 -1
|
vertex 0 -101.600006 -25.400002
|
||||||
vertex 0 -0 0
|
vertex 0 -0 0
|
||||||
vertex 0 -0 -1
|
vertex 0 -0 -25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 -4 -1
|
vertex 0 -101.600006 -25.400002
|
||||||
vertex 0 -0 -1
|
vertex 0 -0 -25.400002
|
||||||
vertex 3.0950184 -4 -1
|
vertex 78.613464 -101.600006 -25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 3.0950184 -4 -1
|
vertex 78.613464 -101.600006 -25.400002
|
||||||
vertex 0 -0 -1
|
vertex 0 -0 -25.400002
|
||||||
vertex 3.0950184 -0 -1
|
vertex 78.613464 -0 -25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -0.57357645 0 -0.81915206
|
facet normal -0.5735764 0 -0.8191522
|
||||||
outer loop
|
outer loop
|
||||||
vertex 3.0950184 -4 -1
|
vertex 78.613464 -101.600006 -25.400002
|
||||||
vertex 3.0950184 -0 -1
|
vertex 78.613464 -0 -25.400002
|
||||||
vertex 5.9513144 -4 -3
|
vertex 151.16339 -101.600006 -76.2
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -0.57357645 0 -0.81915206
|
facet normal -0.5735764 0 -0.8191522
|
||||||
outer loop
|
outer loop
|
||||||
vertex 5.9513144 -4 -3
|
vertex 151.16339 -101.600006 -76.2
|
||||||
vertex 3.0950184 -0 -1
|
vertex 78.613464 -0 -25.400002
|
||||||
vertex 5.9513144 -0 -3
|
vertex 151.16339 -0 -76.2
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 5.9513144 -4 -3
|
vertex 151.16339 -101.600006 -76.2
|
||||||
vertex 5.9513144 -0 -3
|
vertex 151.16339 -0 -76.2
|
||||||
vertex 9.5 -4 -3
|
vertex 241.3 -101.600006 -76.2
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 9.5 -4 -3
|
vertex 241.3 -101.600006 -76.2
|
||||||
vertex 5.9513144 -0 -3
|
vertex 151.16339 -0 -76.2
|
||||||
vertex 9.5 -0 -3
|
vertex 241.3 -0 -76.2
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 1 0 0
|
facet normal 1 0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 9.5 -4 -3
|
vertex 241.3 -101.600006 -76.2
|
||||||
vertex 9.5 -0 -3
|
vertex 241.3 -0 -76.2
|
||||||
vertex 9.5 -4 -2.5
|
vertex 241.3 -101.600006 -63.5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 1 -0 0
|
facet normal 1 -0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 9.5 -4 -2.5
|
vertex 241.3 -101.600006 -63.5
|
||||||
vertex 9.5 -0 -3
|
vertex 241.3 -0 -76.2
|
||||||
vertex 9.5 -0 -2.5
|
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
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -0 0.99999994
|
facet normal 0 -0 0.99999994
|
||||||
outer loop
|
outer loop
|
||||||
vertex 9.5 -4 -2.5
|
vertex 241.3 -101.600006 50.800003
|
||||||
vertex 9.5 -0 -2.5
|
vertex 241.3 -0 50.800003
|
||||||
vertex 6.108964 -4 -2.5
|
vertex 143.37048 -101.600006 50.800003
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 0.99999994
|
facet normal 0 0 0.99999994
|
||||||
outer loop
|
outer loop
|
||||||
vertex 6.108964 -4 -2.5
|
vertex 143.37048 -101.600006 50.800003
|
||||||
vertex 9.5 -0 -2.5
|
vertex 241.3 -0 50.800003
|
||||||
vertex 6.108964 -0 -2.5
|
vertex 143.37048 -0 50.800003
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0.5735763 0 0.8191522
|
facet normal -0.42261827 0 0.9063078
|
||||||
outer loop
|
outer loop
|
||||||
vertex 3.4311862 -4 -0.625
|
vertex 143.37048 -101.600006 50.800003
|
||||||
vertex 4.323779 -4 -1.25
|
vertex 143.37048 -0 50.800003
|
||||||
vertex 4.323779 -0 -1.25
|
vertex 88.9 -101.600006 25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0.57357645 0 0.819152
|
facet normal -0.42261827 0 0.9063078
|
||||||
outer loop
|
outer loop
|
||||||
vertex 4.323779 -4 -1.25
|
vertex 88.9 -101.600006 25.400002
|
||||||
vertex 6.108964 -4 -2.5
|
vertex 143.37048 -0 50.800003
|
||||||
vertex 6.108964 -0 -2.5
|
vertex 88.9 -0 25.400002
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0.57357645 0 0.819152
|
|
||||||
outer loop
|
|
||||||
vertex 3.4311862 -0 -0.625
|
|
||||||
vertex 2.5385938 -0 0
|
|
||||||
vertex 2.5385938 -4 0
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0.57357645 -0 0.819152
|
|
||||||
outer loop
|
|
||||||
vertex 3.4311862 -4 -0.625
|
|
||||||
vertex 3.4311862 -0 -0.625
|
|
||||||
vertex 2.5385938 -4 0
|
|
||||||
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.5735763 0 0.8191522
|
|
||||||
outer loop
|
|
||||||
vertex 3.4311862 -0 -0.625
|
|
||||||
vertex 3.4311862 -4 -0.625
|
|
||||||
vertex 4.323779 -0 -1.25
|
|
||||||
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 4.146974 -0 0.75
|
|
||||||
vertex 4.146974 -4 0.75
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0.42261833 0 -0.90630776
|
|
||||||
outer loop
|
|
||||||
vertex 4.146974 -0 0.75
|
|
||||||
vertex 5.755354 -0 1.5
|
|
||||||
vertex 5.755354 -4 1.5
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0.42261824 0 -0.9063078
|
|
||||||
outer loop
|
|
||||||
vertex 3.342784 -4 0.375
|
|
||||||
vertex 2.5385938 -0 0
|
|
||||||
vertex 3.342784 -0 0.375
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0.42261833 0 -0.90630776
|
|
||||||
outer loop
|
|
||||||
vertex 5.755354 -4 1.5
|
|
||||||
vertex 4.146974 -4 0.75
|
|
||||||
vertex 4.146974 -0 0.75
|
|
||||||
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
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -0 1
|
facet normal 0 -0 1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 9.5 -4 2
|
vertex 88.9 -101.600006 25.400002
|
||||||
vertex 9.5 -0 2
|
vertex 88.9 -0 25.400002
|
||||||
vertex 5.644507 -4 2
|
vertex 0 -101.600006 25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 1
|
facet normal 0 0 1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 5.644507 -4 2
|
vertex 0 -101.600006 25.400002
|
||||||
vertex 9.5 -0 2
|
vertex 88.9 -0 25.400002
|
||||||
vertex 5.644507 -0 2
|
vertex 0 -0 25.400002
|
||||||
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
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -1 0 0
|
facet normal -1 0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 -4 1
|
vertex 0 -101.600006 25.400002
|
||||||
vertex 0 -0 1
|
vertex 0 -0 25.400002
|
||||||
vertex 0 -4 0
|
vertex 0 -101.600006 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -1 0 0
|
facet normal -1 0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 -4 0
|
vertex 0 -101.600006 0
|
||||||
vertex 0 -0 1
|
vertex 0 -0 25.400002
|
||||||
vertex 0 -0 0
|
vertex 0 -0 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 1 -0
|
facet normal 0 1 -0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 3.342784 -0 0.375
|
vertex 84.906715 -0 9.525
|
||||||
vertex 2.5385938 -0 0
|
vertex 64.480286 -0 0
|
||||||
vertex 3.5 -0 1
|
vertex 88.9 -0 25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 1 0
|
facet normal 0 1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 3.4311862 -0 -0.625
|
vertex 105.33314 -0 19.05
|
||||||
vertex 4.323779 -0 -1.25
|
vertex 84.906715 -0 9.525
|
||||||
vertex 3.0950184 -0 -1
|
vertex 88.9 -0 25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 1 0
|
facet normal 0 1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 3.342784 -0 0.375
|
vertex 87.15214 -0 -15.875
|
||||||
vertex 3.5 -0 1
|
vertex 109.82398 -0 -31.75
|
||||||
vertex 4.146974 -0 0.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
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0.99999994 0
|
facet normal 0 0.99999994 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 4.323779 -0 -1.25
|
vertex 64.480286 -0 0
|
||||||
vertex 5.9513144 -0 -3
|
vertex 87.15214 -0 -15.875
|
||||||
vertex 3.0950184 -0 -1
|
vertex 78.613464 -0 -25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 1 0
|
facet normal 0 1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 -0 -1
|
vertex 109.82398 -0 -31.75
|
||||||
vertex 2.5385938 -0 0
|
vertex 151.16339 -0 -76.2
|
||||||
vertex 3.0950184 -0 -1
|
vertex 78.613464 -0 -25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 1 0
|
facet normal 0 1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 -0 -1
|
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 0
|
vertex 0 -0 0
|
||||||
vertex 2.5385938 -0 0
|
vertex 64.480286 -0 0
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 0.99999994 -0
|
|
||||||
outer loop
|
|
||||||
vertex 9.5 -0 -3
|
|
||||||
vertex 6.108964 -0 -2.5
|
|
||||||
vertex 9.5 -0 -2.5
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 1 0
|
|
||||||
outer loop
|
|
||||||
vertex 9.5 -0 -3
|
|
||||||
vertex 5.9513144 -0 -3
|
|
||||||
vertex 6.108964 -0 -2.5
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 1 -0
|
|
||||||
outer loop
|
|
||||||
vertex 5.9513144 -0 -3
|
|
||||||
vertex 4.323779 -0 -1.25
|
|
||||||
vertex 6.108964 -0 -2.5
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 1 0
|
|
||||||
outer loop
|
|
||||||
vertex 5.644507 -0 2
|
|
||||||
vertex 5.755354 -0 1.5
|
|
||||||
vertex 4.146974 -0 0.75
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 0.99999994 -0
|
|
||||||
outer loop
|
|
||||||
vertex 3.0950184 -0 -1
|
|
||||||
vertex 2.5385938 -0 0
|
|
||||||
vertex 3.4311862 -0 -0.625
|
|
||||||
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 1 -0
|
|
||||||
outer loop
|
|
||||||
vertex 9.5 -0 1.5
|
|
||||||
vertex 5.755354 -0 1.5
|
|
||||||
vertex 9.5 -0 2
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 1 -0
|
|
||||||
outer loop
|
|
||||||
vertex 5.755354 -0 1.5
|
|
||||||
vertex 5.644507 -0 2
|
|
||||||
vertex 9.5 -0 2
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 1 0
|
|
||||||
outer loop
|
|
||||||
vertex 2.5385938 -0 0
|
|
||||||
vertex 0 -0 0
|
|
||||||
vertex 0 -0 1
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 1 0
|
|
||||||
outer loop
|
|
||||||
vertex 3.5 -0 1
|
|
||||||
vertex 2.5385938 -0 0
|
|
||||||
vertex 0 -0 1
|
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -0 -1 0
|
facet normal -0 -1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 3.342784 -4 0.375
|
vertex 84.906715 -101.600006 9.525
|
||||||
vertex 3.5 -4 1
|
vertex 88.9 -101.600006 25.400002
|
||||||
vertex 2.5385938 -4 0
|
vertex 64.480286 -101.600006 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -0 -1 0
|
facet normal -0 -1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 4.146974 -4 0.75
|
vertex 105.33314 -101.600006 19.05
|
||||||
vertex 3.5 -4 1
|
vertex 88.9 -101.600006 25.400002
|
||||||
vertex 3.342784 -4 0.375
|
vertex 84.906715 -101.600006 9.525
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -1 -0
|
facet normal 0 -1 -0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 3.4311862 -4 -0.625
|
vertex 87.15214 -101.600006 -15.875
|
||||||
vertex 3.0950184 -4 -1
|
vertex 78.613464 -101.600006 -25.400002
|
||||||
vertex 4.323779 -4 -1.25
|
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
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -0.99999994 0
|
facet normal 0 -0.99999994 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 4.146974 -4 0.75
|
vertex 64.480286 -101.600006 0
|
||||||
vertex 5.755354 -4 1.5
|
vertex 78.613464 -101.600006 -25.400002
|
||||||
vertex 5.644507 -4 2
|
vertex 87.15214 -101.600006 -15.875
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -1 0
|
facet normal -0 -1 -0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 -4 1
|
vertex 109.82398 -101.600006 -31.75
|
||||||
vertex 2.5385938 -4 0
|
vertex 78.613464 -101.600006 -25.400002
|
||||||
vertex 3.5 -4 1
|
vertex 151.16339 -101.600006 -76.2
|
||||||
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
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -0 -1 0
|
facet normal -0 -1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 6.108964 -4 -2.5
|
vertex 155.16768 -101.600006 -63.5
|
||||||
vertex 4.323779 -4 -1.25
|
vertex 109.82398 -101.600006 -31.75
|
||||||
vertex 5.9513144 -4 -3
|
vertex 151.16339 -101.600006 -76.2
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -0 -0.99999994 -0
|
facet normal -0 -1 -0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 9.5 -4 -2.5
|
vertex 241.3 -101.600006 -63.5
|
||||||
vertex 6.108964 -4 -2.5
|
vertex 155.16768 -101.600006 -63.5
|
||||||
vertex 9.5 -4 -3
|
vertex 241.3 -101.600006 -76.2
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -1 -0
|
facet normal 0 -1 -0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 6.108964 -4 -2.5
|
vertex 155.16768 -101.600006 -63.5
|
||||||
vertex 5.9513144 -4 -3
|
vertex 151.16339 -101.600006 -76.2
|
||||||
vertex 9.5 -4 -3
|
vertex 241.3 -101.600006 -76.2
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -1 -0
|
facet normal 0 -1 -0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 2.5385938 -4 0
|
vertex 64.480286 -101.600006 0
|
||||||
vertex 0 -4 -1
|
vertex 0 -101.600006 -25.400002
|
||||||
vertex 3.0950184 -4 -1
|
vertex 78.613464 -101.600006 -25.400002
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -1 0
|
facet normal 0 -1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 -4 -1
|
vertex 0 -101.600006 -25.400002
|
||||||
vertex 2.5385938 -4 0
|
vertex 64.480286 -101.600006 0
|
||||||
vertex 0 -4 0
|
vertex 0 -101.600006 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
endsolid unnamed
|
endsolid unnamed
|
||||||
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 221 KiB |
@ -1,7 +1,5 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect, Download } from '@playwright/test'
|
||||||
import { secrets } from './secrets'
|
import { secrets } from './secrets'
|
||||||
import { EngineCommand } from '../../src/lang/std/engineConnection'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import { getUtils } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
@ -31,111 +29,7 @@ test.beforeEach(async ({ context, page }) => {
|
|||||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||||
})
|
})
|
||||||
|
|
||||||
test.setTimeout(60000)
|
test.setTimeout(60_000)
|
||||||
|
|
||||||
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 camCmd: EngineCommand = {
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_look_at',
|
|
||||||
center: { x: 0, y: 0, z: 0 },
|
|
||||||
up: { x: 0, y: 0, z: 1 },
|
|
||||||
vantage: { x: 0, y: 85, z: 85 },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
await u.sendCustomCmd(camCmd)
|
|
||||||
await u.waitForCmdReceive('default_camera_look_at')
|
|
||||||
|
|
||||||
// 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 u.waitForCmdReceive('camera_drag_end')
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
||||||
|
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
|
||||||
maxDiffPixels: 100,
|
|
||||||
})
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
await u.sendCustomCmd(camCmd)
|
|
||||||
await u.waitForCmdReceive('default_camera_look_at')
|
|
||||||
|
|
||||||
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 u.waitForCmdReceive('camera_drag_end')
|
|
||||||
await page.waitForTimeout(300)
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
|
||||||
maxDiffPixels: 100,
|
|
||||||
})
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
await u.sendCustomCmd(camCmd)
|
|
||||||
await u.waitForCmdReceive('default_camera_look_at')
|
|
||||||
|
|
||||||
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, 350)
|
|
||||||
await page.mouse.up({ button: 'right' })
|
|
||||||
await page.keyboard.up('Control')
|
|
||||||
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.waitForCmdReceive('camera_drag_end')
|
|
||||||
await page.waitForTimeout(300)
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
|
||||||
maxDiffPixels: 100,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('exports of each format should work', async ({ page, context }) => {
|
test('exports of each format should work', async ({ page, context }) => {
|
||||||
// FYI this test doesn't work with only engine running locally
|
// FYI this test doesn't work with only engine running locally
|
||||||
@ -164,11 +58,11 @@ const part001 = startSketchOn('-XZ')
|
|||||||
|> xLineTo({ to: totalLen, tag: 'seg03' }, %)
|
|> xLineTo({ to: totalLen, tag: 'seg03' }, %)
|
||||||
|> yLine({ length: -armThick, tag: 'seg01' }, %)
|
|> yLine({ length: -armThick, tag: 'seg01' }, %)
|
||||||
|> angledLineThatIntersects({
|
|> angledLineThatIntersects({
|
||||||
angle: _180,
|
angle: HALF_TURN,
|
||||||
offset: -armThick,
|
offset: -armThick,
|
||||||
intersectTag: 'seg04'
|
intersectTag: 'seg04'
|
||||||
}, %)
|
}, %)
|
||||||
|> angledLineToY([segAng('seg04', %) + 180, _0], %)
|
|> angledLineToY([segAng('seg04', %) + 180, ZERO], %)
|
||||||
|> angledLineToY({
|
|> angledLineToY({
|
||||||
angle: -bottomAng,
|
angle: -bottomAng,
|
||||||
to: -totalHeightHalf - armThick,
|
to: -totalHeightHalf - armThick,
|
||||||
@ -177,13 +71,13 @@ const part001 = startSketchOn('-XZ')
|
|||||||
|> xLineTo(segEndX('seg03', %) + 0, %)
|
|> xLineTo(segEndX('seg03', %) + 0, %)
|
||||||
|> yLine(-segLen('seg01', %), %)
|
|> yLine(-segLen('seg01', %), %)
|
||||||
|> angledLineThatIntersects({
|
|> angledLineThatIntersects({
|
||||||
angle: _180,
|
angle: HALF_TURN,
|
||||||
offset: -armThick,
|
offset: -armThick,
|
||||||
intersectTag: 'seg02'
|
intersectTag: 'seg02'
|
||||||
}, %)
|
}, %)
|
||||||
|> angledLineToY([segAng('seg02', %) + 180, -baseHeight], %)
|
|> angledLineToY([segAng('seg02', %) + 180, -baseHeight], %)
|
||||||
|> xLineTo(_0, %)
|
|> xLineTo(ZERO, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(4, %)`
|
|> extrude(4, %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -191,14 +85,11 @@ const part001 = startSketchOn('-XZ')
|
|||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.waitForCmdReceive('extrude')
|
await u.waitForCmdReceive('extrude')
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
await page.getByRole('button', { name: APP_NAME }).click()
|
|
||||||
|
|
||||||
interface Paths {
|
interface Paths {
|
||||||
modelPath: string
|
modelPath: string
|
||||||
imagePath: string
|
imagePath: string
|
||||||
@ -207,19 +98,50 @@ const part001 = startSketchOn('-XZ')
|
|||||||
const doExport = async (
|
const doExport = async (
|
||||||
output: Models['OutputFormat_type']
|
output: Models['OutputFormat_type']
|
||||||
): Promise<Paths> => {
|
): Promise<Paths> => {
|
||||||
await page.getByRole('button', { name: 'Export Model' }).click()
|
await page.getByRole('button', { name: APP_NAME }).click()
|
||||||
|
await expect(
|
||||||
const exportSelect = page.getByTestId('export-type')
|
page.getByRole('button', { name: 'Export Part' })
|
||||||
await exportSelect.selectOption({ label: output.type })
|
).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: 'Export Part' }).click()
|
||||||
|
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||||
|
|
||||||
|
// Go through export via command bar
|
||||||
|
await page.getByRole('option', { name: output.type, exact: false }).click()
|
||||||
|
await page.locator('#arg-form').waitFor({ state: 'detached' })
|
||||||
if ('storage' in output) {
|
if ('storage' in output) {
|
||||||
const storageSelect = page.getByTestId('export-storage')
|
await page.getByTestId('arg-name-storage').waitFor({ timeout: 1000 })
|
||||||
await storageSelect.selectOption({ label: output.storage })
|
await page.getByRole('button', { name: 'storage', exact: false }).click()
|
||||||
|
await page
|
||||||
|
.getByRole('option', { name: output.storage, exact: false })
|
||||||
|
.click()
|
||||||
|
await page.locator('#arg-form').waitFor({ state: 'detached' })
|
||||||
|
}
|
||||||
|
await expect(page.getByText('Confirm Export')).toBeVisible()
|
||||||
|
|
||||||
|
const getPromiseAndResolve = () => {
|
||||||
|
let resolve: any = () => {}
|
||||||
|
const promise = new Promise<Download>((r) => {
|
||||||
|
resolve = r
|
||||||
|
})
|
||||||
|
return [promise, resolve]
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadPromise = page.waitForEvent('download')
|
const [downloadPromise1, downloadResolve1] = getPromiseAndResolve()
|
||||||
await page.getByRole('button', { name: 'Export', exact: true }).click()
|
const [downloadPromise2, downloadResolve2] = getPromiseAndResolve()
|
||||||
const download = await downloadPromise
|
let downloadCnt = 0
|
||||||
|
|
||||||
|
page.on('download', async (download) => {
|
||||||
|
if (downloadCnt === 0) {
|
||||||
|
downloadResolve1(download)
|
||||||
|
} else if (downloadCnt === 1) {
|
||||||
|
downloadResolve2(download)
|
||||||
|
}
|
||||||
|
downloadCnt++
|
||||||
|
})
|
||||||
|
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||||
|
|
||||||
|
// Handle download
|
||||||
|
const download = await downloadPromise1
|
||||||
const downloadLocationer = (extra = '', isImage = false) =>
|
const downloadLocationer = (extra = '', isImage = false) =>
|
||||||
`./e2e/playwright/export-snapshots/${output.type}-${
|
`./e2e/playwright/export-snapshots/${output.type}-${
|
||||||
'storage' in output ? output.storage : ''
|
'storage' in output ? output.storage : ''
|
||||||
@ -229,7 +151,7 @@ const part001 = startSketchOn('-XZ')
|
|||||||
|
|
||||||
if (output.type === 'gltf' && output.storage === 'standard') {
|
if (output.type === 'gltf' && output.storage === 'standard') {
|
||||||
// wait for second download
|
// wait for second download
|
||||||
const download2 = await page.waitForEvent('download')
|
const download2 = await downloadPromise2
|
||||||
await download.saveAs(downloadLocation)
|
await download.saveAs(downloadLocation)
|
||||||
await download2.saveAs(downloadLocation2)
|
await download2.saveAs(downloadLocation2)
|
||||||
|
|
||||||
@ -366,23 +288,363 @@ const part001 = startSketchOn('-XZ')
|
|||||||
// snapshot exports, good compromise to capture that exports are healthy without getting bogged down in "did the formatting change" changes
|
// 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
|
// context: https://github.com/KittyCAD/modeling-app/issues/1222
|
||||||
for (const { modelPath, imagePath, outputType } of exportLocations) {
|
for (const { modelPath, imagePath, outputType } of exportLocations) {
|
||||||
const cliCommand = `export KITTYCAD_TOKEN=${secrets.snapshottoken} && kittycad file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}`
|
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 child = spawn(cliCommand, { shell: true })
|
const child = spawn(cliCommand, { shell: true })
|
||||||
await new Promise((resolve, reject) => {
|
const result = await new Promise<string>((resolve, reject) => {
|
||||||
child.on('error', (code: any, msg: any) => {
|
child.on('error', (code: any, msg: any) => {
|
||||||
console.log('error', code, msg)
|
console.log('error', code, msg)
|
||||||
reject()
|
reject('error')
|
||||||
})
|
})
|
||||||
child.on('exit', (code, msg) => {
|
child.on('exit', (code, msg) => {
|
||||||
console.log('exit', code, msg)
|
console.log('exit', code, msg)
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
reject(`exit code ${code} for model ${modelPath}`)
|
reject(`exit code ${code} for model ${modelPath}`)
|
||||||
} else {
|
} else {
|
||||||
resolve(true)
|
resolve('success')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
child.stderr.on('data', (data) => console.log(`stderr: ${data}`))
|
child.stderr.on('data', (data) => console.log(`stderr: ${data}`))
|
||||||
child.stdout.on('data', (data) => console.log(`stdout: ${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}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('extrude on each default plane should be stable', async ({
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
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], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(10.00, %)
|
||||||
|
`
|
||||||
|
await context.addInitScript(async (code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, makeCode('XY'))
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await page.getByText('Code').click()
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
await page.getByText('Code').click()
|
||||||
|
|
||||||
|
const runSnapshotsForOtherPlanes = async (plane = 'XY') => {
|
||||||
|
// clear code
|
||||||
|
await u.removeCurrentCode()
|
||||||
|
// add makeCode('XZ')
|
||||||
|
await page.locator('.cm-content').fill(makeCode(plane))
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await page.getByText('Code').click()
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
await page.getByText('Code').click()
|
||||||
|
}
|
||||||
|
await runSnapshotsForOtherPlanes('-XY')
|
||||||
|
|
||||||
|
await runSnapshotsForOtherPlanes('XZ')
|
||||||
|
await runSnapshotsForOtherPlanes('-XZ')
|
||||||
|
|
||||||
|
await runSnapshotsForOtherPlanes('YZ')
|
||||||
|
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',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
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.move(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
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.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 53 KiB |
@ -80,10 +80,21 @@ export function getUtils(page: Page) {
|
|||||||
waitForAuthSkipAppStart: () => waitForPageLoad(page),
|
waitForAuthSkipAppStart: () => waitForPageLoad(page),
|
||||||
removeCurrentCode: () => removeCurrentCode(page),
|
removeCurrentCode: () => removeCurrentCode(page),
|
||||||
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
||||||
|
updateCamPosition: async (xyz: [number, number, number]) => {
|
||||||
|
const fillInput = async () => {
|
||||||
|
await page.fill('[data-testid="cam-x-position"]', String(xyz[0]))
|
||||||
|
await page.fill('[data-testid="cam-y-position"]', String(xyz[1]))
|
||||||
|
await page.fill('[data-testid="cam-z-position"]', String(xyz[2]))
|
||||||
|
}
|
||||||
|
await fillInput()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await fillInput()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await fillInput()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
},
|
||||||
clearCommandLogs: () => clearCommandLogs(page),
|
clearCommandLogs: () => clearCommandLogs(page),
|
||||||
expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr),
|
expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr),
|
||||||
waitForDefaultPlanesVisibilityChange: () =>
|
|
||||||
waitForDefaultPlanesToBeVisible(page),
|
|
||||||
openDebugPanel: () => openDebugPanel(page),
|
openDebugPanel: () => openDebugPanel(page),
|
||||||
closeDebugPanel: () => closeDebugPanel(page),
|
closeDebugPanel: () => closeDebugPanel(page),
|
||||||
openAndClearDebugPanel: async () => {
|
openAndClearDebugPanel: async () => {
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
import { browser, $, expect } from '@wdio/globals'
|
import { browser, $, expect } from '@wdio/globals'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
|
|
||||||
describe('KCMA (Tauri, Linux)', () => {
|
const defaultDir = `${process.env.HOME}/Documents/zoo-modeling-app-projects`
|
||||||
it('opens the auth page, signs in, and signs out', async () => {
|
const userCodeDir = '/tmp/kittycad_user_code'
|
||||||
// Clean up previous tests
|
|
||||||
|
async function click(element: WebdriverIO.Element): Promise<void> {
|
||||||
|
// Workaround for .click(), see https://github.com/tauri-apps/tauri/issues/6541
|
||||||
|
await element.waitForClickable()
|
||||||
|
await browser.execute('arguments[0].click();', element)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ZMA (Tauri, Linux)', () => {
|
||||||
|
it('opens the auth page and signs in', async () => {
|
||||||
|
// Clean up filesystem from previous tests
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
await fs.rm('/tmp/kittycad_user_code', { force: true })
|
await fs.rm(defaultDir, { force: true, recursive: true })
|
||||||
await browser.execute('window.localStorage.clear()')
|
await fs.rm(userCodeDir, { force: true })
|
||||||
|
|
||||||
const signInButton = await $('[data-testid="sign-in-button"]')
|
const signInButton = await $('[data-testid="sign-in-button"]')
|
||||||
expect(await signInButton.getText()).toEqual('Sign in')
|
expect(await signInButton.getText()).toEqual('Sign in')
|
||||||
|
|
||||||
// Workaround for .click(), see https://github.com/tauri-apps/tauri/issues/6541
|
await click(signInButton)
|
||||||
await signInButton.waitForClickable()
|
|
||||||
await browser.execute('arguments[0].click();', signInButton)
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||||
|
|
||||||
// Get from main.rs
|
// Get from main.rs
|
||||||
@ -49,14 +56,49 @@ describe('KCMA (Tauri, Linux)', () => {
|
|||||||
// Now should be signed in
|
// Now should be signed in
|
||||||
const newFileButton = await $('[data-testid="home-new-file"]')
|
const newFileButton = await $('[data-testid="home-new-file"]')
|
||||||
expect(await newFileButton.getText()).toEqual('New file')
|
expect(await newFileButton.getText()).toEqual('New file')
|
||||||
|
})
|
||||||
|
|
||||||
// So let's sign out!
|
it('opens the settings page, checks filesystem settings, and closes the settings page', async () => {
|
||||||
const menuButton = await $('[data-testid="user-sidebar-toggle"]')
|
const menuButton = await $('[data-testid="user-sidebar-toggle"]')
|
||||||
await menuButton.waitForClickable()
|
await click(menuButton)
|
||||||
await browser.execute('arguments[0].click();', menuButton)
|
|
||||||
|
const settingsButton = await $('[data-testid="settings-button"]')
|
||||||
|
await click(settingsButton)
|
||||||
|
|
||||||
|
const defaultDirInput = await $('[data-testid="default-directory-input"]')
|
||||||
|
expect(await defaultDirInput.getValue()).toEqual(defaultDir)
|
||||||
|
|
||||||
|
const nameInput = await $('[data-testid="name-input"]')
|
||||||
|
expect(await nameInput.getValue()).toEqual('project-$nnn')
|
||||||
|
|
||||||
|
const closeButton = await $('[data-testid="close-button"]')
|
||||||
|
await click(closeButton)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks that no file exists, creates a new file', async () => {
|
||||||
|
const homeSection = await $('[data-testid="home-section"]')
|
||||||
|
expect(await homeSection.getText()).toContain('No Projects found')
|
||||||
|
|
||||||
|
const newFileButton = await $('[data-testid="home-new-file"]')
|
||||||
|
await click(newFileButton)
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
expect(await homeSection.getText()).toContain('project-000')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('opens the new file and expects a loading stream', async () => {
|
||||||
|
const projectLink = await $('[data-testid="project-link"]')
|
||||||
|
await click(projectLink)
|
||||||
|
const loadingText = await $('[data-testid="loading-stream"]')
|
||||||
|
expect(await loadingText.getText()).toContain('Loading stream...')
|
||||||
|
await browser.execute('window.location.href = "tauri://localhost/home"')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('signs out', async () => {
|
||||||
|
const menuButton = await $('[data-testid="user-sidebar-toggle"]')
|
||||||
|
await click(menuButton)
|
||||||
const signoutButton = await $('[data-testid="user-sidebar-sign-out"]')
|
const signoutButton = await $('[data-testid="user-sidebar-sign-out"]')
|
||||||
await signoutButton.waitForClickable()
|
await click(signoutButton)
|
||||||
await browser.execute('arguments[0].click();', signoutButton)
|
|
||||||
const newSignInButton = await $('[data-testid="sign-in-button"]')
|
const newSignInButton = await $('[data-testid="sign-in-button"]')
|
||||||
expect(await newSignInButton.getText()).toEqual('Sign in')
|
expect(await newSignInButton.getText()).toEqual('Sign in')
|
||||||
})
|
})
|
||||||
|
@ -11,8 +11,13 @@
|
|||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<script defer data-domain="app.kittycad.io" src="https://plausible.corp.kittycad.io/js/script.js"></script>
|
<link rel="stylesheet" href="https://use.typekit.net/zzv8rvm.css" />
|
||||||
<title>Modeling App</title>
|
<script
|
||||||
|
defer
|
||||||
|
data-domain="app.zoo.dev"
|
||||||
|
src="https://plausible.corp.zoo.dev/js/script.js"
|
||||||
|
></script>
|
||||||
|
<title>Zoo Modeling App</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="body-bg">
|
<body class="body-bg">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
69
make-release.sh
Executable file
@ -0,0 +1,69 @@
|
|||||||
|
#!/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 ""
|
28
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.13.0",
|
"version": "0.15.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.10.2",
|
"@codemirror/autocomplete": "^6.10.2",
|
||||||
@ -10,17 +10,17 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.17",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.46",
|
"@kittycad/lib": "^0.0.54",
|
||||||
"@lezer/javascript": "^1.4.9",
|
"@lezer/javascript": "^1.4.9",
|
||||||
"@open-rpc/client-js": "^1.8.1",
|
"@open-rpc/client-js": "^1.8.1",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
"@replit/codemirror-interact": "^6.3.0",
|
"@replit/codemirror-interact": "^6.3.0",
|
||||||
"@sentry/react": "^7.77.0",
|
|
||||||
"@tauri-apps/api": "^1.5.1",
|
"@tauri-apps/api": "^1.5.1",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/user-event": "^14.5.1",
|
"@testing-library/user-event": "^14.5.1",
|
||||||
"@ts-stack/markdown": "^1.5.0",
|
"@ts-stack/markdown": "^1.5.0",
|
||||||
|
"@tweenjs/tween.js": "^23.1.1",
|
||||||
"@types/node": "^16.7.13",
|
"@types/node": "^16.7.13",
|
||||||
"@types/react": "^18.2.41",
|
"@types/react": "^18.2.41",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
@ -33,6 +33,7 @@
|
|||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"json-rpc-2.0": "^1.6.0",
|
"json-rpc-2.0": "^1.6.0",
|
||||||
|
"node-fetch": "^3.3.2",
|
||||||
"re-resizable": "^6.9.11",
|
"re-resizable": "^6.9.11",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@ -45,11 +46,12 @@
|
|||||||
"sketch-helpers": "^0.0.4",
|
"sketch-helpers": "^0.0.4",
|
||||||
"swr": "^2.2.2",
|
"swr": "^2.2.2",
|
||||||
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
|
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
|
||||||
|
"three": "^0.160.0",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"vitest": "^0.34.6",
|
"vitest": "^1.3.1",
|
||||||
"vscode-jsonrpc": "^8.1.0",
|
"vscode-jsonrpc": "^8.1.0",
|
||||||
"vscode-languageserver-protocol": "^3.17.5",
|
"vscode-languageserver-protocol": "^3.17.5",
|
||||||
"wasm-pack": "^0.12.1",
|
"wasm-pack": "^0.12.1",
|
||||||
@ -70,7 +72,6 @@
|
|||||||
"test": "vitest --mode development",
|
"test": "vitest --mode development",
|
||||||
"test:nowatch": "vitest run --mode development",
|
"test:nowatch": "vitest run --mode development",
|
||||||
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
|
"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",
|
"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:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||||
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||||
@ -82,7 +83,9 @@
|
|||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||||
"lint": "eslint --fix src",
|
"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"
|
"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",
|
||||||
|
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\""
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
@ -109,21 +112,21 @@
|
|||||||
"@tauri-apps/cli": "^1.5.6",
|
"@tauri-apps/cli": "^1.5.6",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/debounce-promise": "^3.1.8",
|
"@types/debounce-promise": "^3.1.8",
|
||||||
"@types/isomorphic-fetch": "^0.0.36",
|
|
||||||
"@types/pixelmatch": "^5.2.6",
|
"@types/pixelmatch": "^5.2.6",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/react-modal": "^3.16.3",
|
"@types/react-modal": "^3.16.3",
|
||||||
|
"@types/three": "^0.160.0",
|
||||||
"@types/uuid": "^9.0.4",
|
"@types/uuid": "^9.0.4",
|
||||||
"@types/wait-on": "^5.3.4",
|
"@types/wait-on": "^5.3.4",
|
||||||
"@types/wicg-file-system-access": "^2020.9.6",
|
"@types/wicg-file-system-access": "^2020.9.6",
|
||||||
"@types/ws": "^8.5.5",
|
"@types/ws": "^8.5.5",
|
||||||
"@vitejs/plugin-react": "^4.1.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"@vitest/coverage-istanbul": "^0.34.6",
|
|
||||||
"@wdio/cli": "^8.24.3",
|
"@wdio/cli": "^8.24.3",
|
||||||
"@wdio/globals": "^8.24.3",
|
"@wdio/globals": "^8.24.3",
|
||||||
"@wdio/local-runner": "^8.24.3",
|
"@wdio/local-runner": "^8.24.3",
|
||||||
"@wdio/mocha-framework": "^8.24.3",
|
"@wdio/mocha-framework": "^8.24.3",
|
||||||
"@wdio/spec-reporter": "^8.24.2",
|
"@wdio/spec-reporter": "^8.24.2",
|
||||||
|
"@xstate/cli": "^0.5.17",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.53.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
@ -133,12 +136,15 @@
|
|||||||
"pixelmatch": "^5.3.0",
|
"pixelmatch": "^5.3.0",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.8.0",
|
"prettier": "^2.8.0",
|
||||||
"setimmediate": "^1.0.5",
|
"setimmediate": "^1.0.5",
|
||||||
"tailwindcss": "^3.3.6",
|
"tailwindcss": "^3.3.6",
|
||||||
"vite": "^4.5.0",
|
"vite": "^5.1.3",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-tsconfig-paths": "^4.2.1",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
|
"vite-tsconfig-paths": "^4.3.1",
|
||||||
|
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||||
"wait-on": "^7.2.0",
|
"wait-on": "^7.2.0",
|
||||||
"yarn": "^1.22.19"
|
"yarn": "^1.22.19"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test';
|
import { defineConfig, devices } from '@playwright/test'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
@ -78,5 +78,4 @@ export default defineConfig({
|
|||||||
// url: 'http://127.0.0.1:3000',
|
// url: 'http://127.0.0.1:3000',
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
47
public/announce_release.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import re
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
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
|
||||||
|
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}
|
||||||
|
''',
|
||||||
|
"username": "Modeling App Release Updates",
|
||||||
|
"avatar_url": "https://raw.githubusercontent.com/KittyCAD/modeling-app/main/public/discord-avatar.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
# POST request to the Discord webhook
|
||||||
|
response = requests.post(webhook_url, json=data)
|
||||||
|
|
||||||
|
# Check for success
|
||||||
|
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"])
|
BIN
public/discord-avatar.png
Normal file
After Width: | Height: | Size: 11 KiB |
@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
First off, thank you so much for your interest in being a part of the closed Alpha program! We are thrilled to have others use our product and see what you build with it (and truthfully, how you break it too).
|
First off, thank you so much for your interest in being a part of the closed Alpha program! We are thrilled to have others use our product and see what you build with it (and truthfully, how you break it too).
|
||||||
|
|
||||||
### KittyCAD Modeling App (KCMA)
|
### Zoo Modeling App (ZMA)
|
||||||
|
|
||||||
What we are introducing to you is our KittyCAD Modeling App (KCMA). KCMA is a CAD application that expresses a hybrid style of traditional CAD interface along with a code-CAD interface. KCMA is a great way for us to test our own APIs as well as inspire others to develop their own applications.
|
What we are introducing to you is our Zoo Modeling App (ZMA). ZMA is a CAD application that expresses a hybrid style of traditional CAD interface along with a code-CAD interface. ZMA is a great way for us to test our own APIs as well as inspire others to develop their own applications.
|
||||||
|
|
||||||
### Why Code?
|
### Why Code?
|
||||||
|
|
||||||
@ -18,11 +18,11 @@ Plenty of you have professional CAD experience, and may not understand why codin
|
|||||||
- Reproducibility
|
- Reproducibility
|
||||||
- Easier integration with other tools
|
- Easier integration with other tools
|
||||||
|
|
||||||
### Before You Use KCMA
|
### Before You Use ZMA
|
||||||
|
|
||||||
Before you dive straight into the app, we wanted to lay some expectations out for you.
|
Before you dive straight into the app, we wanted to lay some expectations out for you.
|
||||||
|
|
||||||
- KCMA is in early development. Kurt pitched the idea back in January, and the team has been working hard on it since then. KCMA has really basic CAD features for now, but we have plenty of features on our roadmap. Most of the features that you may be currently used to in your CAD workflow today will be available down the road.
|
- ZMA is in early development. Kurt pitched the idea back in January, and the team has been working hard on it since then. ZMA has really basic CAD features for now, but we have plenty of features on our roadmap. Most of the features that you may be currently used to in your CAD workflow today will be available down the road.
|
||||||
- For a list of all scripting functions, please reference our [documentation](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md). For a basic rundown of our types, please reference [this document](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/types.md).
|
- For a list of all scripting functions, please reference our [documentation](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md). For a basic rundown of our types, please reference [this document](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/types.md).
|
||||||
- With that being said, we have created an external new features list in [GH Discussions](https://github.com/KittyCAD/modeling-app/discussions). For our current priority list, please click [here](https://github.com/KittyCAD/modeling-app/blob/main/public/roadmap.md). Please upvote any features in the GH Discussions page that you would like to see implemented first. We will prioritize the highest upvoted items or items that are foundational for other features on the list. You can also add your own, but we will review it to make sure it’s not a duplicate or it’s feasible for the current state of the app.
|
- With that being said, we have created an external new features list in [GH Discussions](https://github.com/KittyCAD/modeling-app/discussions). For our current priority list, please click [here](https://github.com/KittyCAD/modeling-app/blob/main/public/roadmap.md). Please upvote any features in the GH Discussions page that you would like to see implemented first. We will prioritize the highest upvoted items or items that are foundational for other features on the list. You can also add your own, but we will review it to make sure it’s not a duplicate or it’s feasible for the current state of the app.
|
||||||
- Please report any and all bugs/issues you find. Even the smallest bugs are important! You can report them in a GH Issue [here](https://github.com/KittyCAD/modeling-app/issues/new). You are more than welcome to link your GH Issue in the **bugs** section of our Discord, but if you want to discuss the bug further, please keep that in the GH Issue thread. Please include the severity of the bug in your GH Issue ticket (High, Medium, or Low). If you are having trouble deciding what severity the bug is, use this guideline:
|
- Please report any and all bugs/issues you find. Even the smallest bugs are important! You can report them in a GH Issue [here](https://github.com/KittyCAD/modeling-app/issues/new). You are more than welcome to link your GH Issue in the **bugs** section of our Discord, but if you want to discuss the bug further, please keep that in the GH Issue thread. Please include the severity of the bug in your GH Issue ticket (High, Medium, or Low). If you are having trouble deciding what severity the bug is, use this guideline:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"short_name": "KCMA",
|
"short_name": "ZMA",
|
||||||
"name": "KittyCAD Modeling App",
|
"name": "Zoo Modeling App",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
|
117
src-tauri/Cargo.lock
generated
@ -67,9 +67,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.75"
|
version = "1.0.80"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "app"
|
name = "app"
|
||||||
@ -95,7 +95,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -542,7 +542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -552,7 +552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
|
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -582,7 +582,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -593,7 +593,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -899,7 +899,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1242,9 +1242,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.20"
|
version = "0.3.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049"
|
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@ -1252,7 +1252,7 @@ dependencies = [
|
|||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"indexmap 1.9.3",
|
"indexmap 2.0.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@ -1664,9 +1664,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad"
|
name = "kittycad"
|
||||||
version = "0.2.42"
|
version = "0.2.59"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6aa554d86b6dbbd976a659c912ae25ce817b4378eb12a5684907e263410f0a7b"
|
checksum = "4080db4364c103601db486e4a8aa889ea56c011991e4c454373d8050a165d3da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1934,9 +1934,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.9"
|
version = "0.8.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
@ -2215,7 +2215,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2368,7 +2368,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.7.5",
|
"regex-syntax 0.7.5",
|
||||||
"structmeta",
|
"structmeta",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2487,7 +2487,7 @@ dependencies = [
|
|||||||
"phf_shared 0.11.2",
|
"phf_shared 0.11.2",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2555,7 +2555,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2657,9 +2657,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.67"
|
version = "1.0.78"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@ -2675,9 +2675,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.33"
|
version = "1.0.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@ -3235,9 +3235,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.193"
|
version = "1.0.197"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -3253,13 +3253,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.193"
|
version = "1.0.197"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3275,10 +3275,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.108"
|
version = "1.0.112"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap 2.0.0",
|
||||||
"itoa 1.0.6",
|
"itoa 1.0.6",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3302,7 +3303,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3352,7 +3353,7 @@ dependencies = [
|
|||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3556,7 +3557,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"structmeta-derive",
|
"structmeta-derive",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3567,7 +3568,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3605,9 +3606,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.33"
|
version = "2.0.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
|
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -3760,14 +3761,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "1.5.3"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32d563b672acde8d0cc4c1b1f5b855976923f67e8d6fe1eba51df0211e197be2"
|
checksum = "0da520ff07c0745199204f7a7a62a8c6ee1666313b792b051ca170eca04649aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
|
"dunce",
|
||||||
"embed_plist",
|
"embed_plist",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"flate2",
|
"flate2",
|
||||||
@ -3778,6 +3780,7 @@ dependencies = [
|
|||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"http",
|
"http",
|
||||||
"ignore",
|
"ignore",
|
||||||
|
"indexmap 1.9.3",
|
||||||
"objc",
|
"objc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open",
|
"open",
|
||||||
@ -3812,9 +3815,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-build"
|
name = "tauri-build"
|
||||||
version = "1.5.0"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "defbfc551bd38ab997e5f8e458f87396d2559d05ce32095076ad6c30f7fc5f9c"
|
checksum = "e9914a4715e0b75d9f387a285c7e26b5bbfeb1249ad9f842675a82481565c532"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
@ -3831,9 +3834,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "1.4.1"
|
version = "1.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b3475e55acec0b4a50fb96435f19631fb58cbcd31923e1a213de5c382536bbb"
|
checksum = "a1554c5857f65dbc377cefb6b97c8ac77b1cb2a90d30d3448114d5d6b48a77fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"brotli",
|
"brotli",
|
||||||
@ -3857,9 +3860,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "1.4.2"
|
version = "1.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acea6445eececebd72ed7720cfcca46eee3b5bad8eb408be8f7ef2e3f7411500"
|
checksum = "277abf361a3a6993ec16bcbb179de0d6518009b851090a01adfea12ac89fa875"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -3872,7 +3875,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs-extra"
|
name = "tauri-plugin-fs-extra"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#537053d3171a7374a1a86fed422523e7b45a4fb8"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#19aa2204115e7304681cb40faf7512aba525bc5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3883,9 +3886,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "0.14.1"
|
version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07f8e9e53e00e9f41212c115749e87d5cd2a9eebccafca77a19722eeecd56d43"
|
checksum = "cf2d0652aa2891ff3e9caa2401405257ea29ab8372cce01f186a5825f1bd0e76"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
@ -3904,9 +3907,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "0.14.2"
|
version = "0.14.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "803a01101bc611ba03e13329951a1bde44287a54234189b9024b78619c1bc206"
|
checksum = "067c56fc153b3caf406d7cd6de4486c80d1d66c0f414f39e94cb2f5543f6445f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"gtk",
|
"gtk",
|
||||||
@ -3924,9 +3927,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "1.5.1"
|
version = "1.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a52165bb340e6f6a75f1f5eeeab1bb49f861c12abe3a176067d53642b5454986"
|
checksum = "75ad0bbb31fccd1f4c56275d0a5c3abdf1f59999f72cb4ef8b79b4ed42082a21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brotli",
|
"brotli",
|
||||||
"ctor",
|
"ctor",
|
||||||
@ -4009,7 +4012,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4051,9 +4054,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.34.0"
|
version = "1.36.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
|
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -4193,7 +4196,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4443,7 +4446,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -4477,7 +4480,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.33",
|
"syn 2.0.48",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -12,17 +12,17 @@ rust-version = "1.60"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "1.5.0", features = [] }
|
tauri-build = { version = "1.5.1", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
kittycad = "0.2.42"
|
kittycad = "0.2.59"
|
||||||
oauth2 = "4.4.2"
|
oauth2 = "4.4.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tauri = { version = "1.5.3", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] }
|
tauri = { version = "1.6.0", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] }
|
||||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tokio = { version = "1.34.0", features = ["time"] }
|
tokio = { version = "1.36.0", features = ["time"] }
|
||||||
toml = "0.8.2"
|
toml = "0.8.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 69 KiB |
@ -6,8 +6,8 @@
|
|||||||
"distDir": "../build"
|
"distDir": "../build"
|
||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "kittycad-modeling",
|
"productName": "zoo-modeling-app",
|
||||||
"version": "0.13.0"
|
"version": "0.15.5"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
@ -84,7 +84,7 @@
|
|||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"height": 1200,
|
"height": 1200,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"title": "KittyCAD Modeling",
|
"title": "Zoo Modeling App",
|
||||||
"width": 1800
|
"width": 1800
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "KittyCAD Modeling"
|
"productName": "Zoo Modeling App"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"updater": {
|
"updater": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://dl.kittycad.io/releases/modeling-app/last_update.json"
|
"https://dl.zoo.dev/releases/modeling-app/last_update.json"
|
||||||
],
|
],
|
||||||
"dialog": true,
|
"dialog": true,
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUzNzA4MjBEQjFBRTY4NzYKUldSMmFLNnhEWUp3NCtsT21Jd05wQktOaGVkOVp6MUFma0hNTDRDSnI2RkJJTEZOWG1ncFhqcU8K"
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUzNzA4MjBEQjFBRTY4NzYKUldSMmFLNnhEWUp3NCtsT21Jd05wQktOaGVkOVp6MUFma0hNTDRDSnI2RkJJTEZOWG1ncFhqcU8K"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "KittyCAD Modeling"
|
"productName": "Zoo Modeling App"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react'
|
|
||||||
import { App } from './App'
|
|
||||||
import { describe, test, vi } from 'vitest'
|
|
||||||
import {
|
|
||||||
Route,
|
|
||||||
RouterProvider,
|
|
||||||
createMemoryRouter,
|
|
||||||
createRoutesFromElements,
|
|
||||||
} from 'react-router-dom'
|
|
||||||
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
|
||||||
import CommandBarProvider from 'components/CommandBar/CommandBar'
|
|
||||||
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
|
||||||
import { BROWSER_FILE_NAME } from 'Router'
|
|
||||||
|
|
||||||
let listener: ((rect: any) => void) | undefined = undefined
|
|
||||||
;(global as any).ResizeObserver = class ResizeObserver {
|
|
||||||
constructor(ls: ((rect: any) => void) | undefined) {
|
|
||||||
listener = ls
|
|
||||||
}
|
|
||||||
observe() {}
|
|
||||||
unobserve() {}
|
|
||||||
disconnect() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('App tests', () => {
|
|
||||||
test('Renders the modeling app screen, including "Variables" pane.', () => {
|
|
||||||
vi.mock('react-router-dom', async () => {
|
|
||||||
const actual = (await vi.importActual('react-router-dom')) as Record<
|
|
||||||
string,
|
|
||||||
any
|
|
||||||
>
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
useParams: () => ({ id: BROWSER_FILE_NAME }),
|
|
||||||
useLoaderData: () => ({ code: null }),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
render(
|
|
||||||
<TestWrap>
|
|
||||||
<App />
|
|
||||||
</TestWrap>
|
|
||||||
)
|
|
||||||
const linkElement = screen.getByText(/Variables/i)
|
|
||||||
expect(linkElement).toBeInTheDocument()
|
|
||||||
|
|
||||||
vi.restoreAllMocks()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function TestWrap({ children }: { children: React.ReactNode }) {
|
|
||||||
// We have to use a memory router in the testing environment,
|
|
||||||
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
|
|
||||||
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
|
|
||||||
const router = createMemoryRouter(
|
|
||||||
createRoutesFromElements(
|
|
||||||
<Route
|
|
||||||
path="/file/:id"
|
|
||||||
element={
|
|
||||||
<CommandBarProvider>
|
|
||||||
<GlobalStateProvider>
|
|
||||||
<ModelingMachineProvider>{children}</ModelingMachineProvider>
|
|
||||||
</GlobalStateProvider>
|
|
||||||
</CommandBarProvider>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
{
|
|
||||||
initialEntries: ['/file/new'],
|
|
||||||
initialIndex: 0,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return <RouterProvider router={router} />
|
|
||||||
}
|
|
84
src/App.tsx
@ -19,21 +19,24 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { getNormalisedCoordinates } from './lib/utils'
|
import { getNormalisedCoordinates } from './lib/utils'
|
||||||
import { useLoaderData } from 'react-router-dom'
|
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||||
import { IndexLoaderData } from './Router'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { onboardingPaths } from 'routes/Onboarding'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
|
||||||
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
|
||||||
import { CodeMenu } from 'components/CodeMenu'
|
import { CodeMenu } from 'components/CodeMenu'
|
||||||
import { TextEditor } from 'components/TextEditor'
|
import { TextEditor } from 'components/TextEditor'
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||||
import { engineCommandManager } from './lang/std/engineConnection'
|
import { engineCommandManager } from './lang/std/engineConnection'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { project, file } = useLoaderData() as IndexLoaderData
|
const { project, file } = useLoaderData() as IndexLoaderData
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const filePath = useAbsoluteFilePath()
|
||||||
|
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
const {
|
const {
|
||||||
@ -51,8 +54,7 @@ export function App() {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const { settings } = useGlobalStateContext()
|
const { settings } = useGlobalStateContext()
|
||||||
const { showDebugPanel, onboardingStatus, cameraControls, theme } =
|
const { showDebugPanel, onboardingStatus, theme } = settings?.context || {}
|
||||||
settings?.context || {}
|
|
||||||
const { state, send } = useModelingContext()
|
const { state, send } = useModelingContext()
|
||||||
|
|
||||||
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
|
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
|
||||||
@ -71,6 +73,16 @@ export function App() {
|
|||||||
useHotkeys('shift + e', () => togglePane('kclErrors'))
|
useHotkeys('shift + e', () => togglePane('kclErrors'))
|
||||||
useHotkeys('shift + d', () => togglePane('debug'))
|
useHotkeys('shift + d', () => togglePane('debug'))
|
||||||
useHotkeys('esc', () => send('Cancel'))
|
useHotkeys('esc', () => send('Cancel'))
|
||||||
|
useHotkeys('backspace', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
})
|
||||||
|
useHotkeys(
|
||||||
|
isTauri() ? 'mod + ,' : 'shift + mod + ,',
|
||||||
|
() => navigate(filePath + paths.SETTINGS),
|
||||||
|
{
|
||||||
|
splitKey: '|',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
||||||
(p) => p === onboardingStatus
|
(p) => p === onboardingStatus
|
||||||
@ -84,9 +96,11 @@ export function App() {
|
|||||||
|
|
||||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||||
engineCommandManager.sendSceneCommand(message)
|
engineCommandManager.sendSceneCommand(message)
|
||||||
}, 16)
|
}, 1000 / 15)
|
||||||
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
e.nativeEvent.preventDefault()
|
if (state.matches('Sketch')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { x, y } = getNormalisedCoordinates({
|
const { x, y } = getNormalisedCoordinates({
|
||||||
clientX: e.clientX,
|
clientX: e.clientX,
|
||||||
@ -97,58 +111,11 @@ export function App() {
|
|||||||
|
|
||||||
const newCmdId = uuidv4()
|
const newCmdId = uuidv4()
|
||||||
if (buttonDownInStream === undefined) {
|
if (buttonDownInStream === undefined) {
|
||||||
if (state.matches('Sketch.Line Tool')) {
|
|
||||||
debounceSocketSend({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: newCmdId,
|
|
||||||
cmd: {
|
|
||||||
type: 'mouse_move',
|
|
||||||
window: { x, y },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
debounceSocketSend({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'highlight_set_entity',
|
|
||||||
selected_at_window: { x, y },
|
|
||||||
},
|
|
||||||
cmd_id: newCmdId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (state.matches('Sketch.Move Tool')) {
|
|
||||||
debounceSocketSend({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: newCmdId,
|
|
||||||
cmd: {
|
|
||||||
type: 'handle_mouse_drag_move',
|
|
||||||
window: { x, y },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
|
||||||
let interaction: CameraDragInteractionType_type
|
|
||||||
|
|
||||||
const eWithButton = { ...e, button: buttonDownInStream }
|
|
||||||
|
|
||||||
if (interactionGuards.pan.callback(eWithButton)) {
|
|
||||||
interaction = 'pan'
|
|
||||||
} else if (interactionGuards.rotate.callback(eWithButton)) {
|
|
||||||
interaction = 'rotate'
|
|
||||||
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
|
||||||
interaction = 'zoom'
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
debounceSocketSend({
|
debounceSocketSend({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'camera_drag_move',
|
type: 'highlight_set_entity',
|
||||||
interaction,
|
selected_at_window: { x, y },
|
||||||
window: { x, y },
|
|
||||||
},
|
},
|
||||||
cmd_id: newCmdId,
|
cmd_id: newCmdId,
|
||||||
})
|
})
|
||||||
@ -238,6 +205,7 @@ export function App() {
|
|||||||
open={openPanes.includes('debug')}
|
open={openPanes.includes('debug')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{/* <CamToggle /> */}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
116
src/Router.tsx
@ -3,21 +3,11 @@ import {
|
|||||||
createBrowserRouter,
|
createBrowserRouter,
|
||||||
Outlet,
|
Outlet,
|
||||||
redirect,
|
redirect,
|
||||||
useLocation,
|
|
||||||
RouterProvider,
|
RouterProvider,
|
||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
import {
|
|
||||||
matchRoutes,
|
|
||||||
createRoutesFromChildren,
|
|
||||||
useNavigationType,
|
|
||||||
} from 'react-router'
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { ErrorPage } from './components/ErrorPage'
|
import { ErrorPage } from './components/ErrorPage'
|
||||||
import { Settings } from './routes/Settings'
|
import { Settings } from './routes/Settings'
|
||||||
import Onboarding, {
|
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
|
||||||
onboardingRoutes,
|
|
||||||
onboardingPaths,
|
|
||||||
} from './routes/Onboarding'
|
|
||||||
import SignIn from './routes/SignIn'
|
import SignIn from './routes/SignIn'
|
||||||
import { Auth } from './Auth'
|
import { Auth } from './Auth'
|
||||||
import { isTauri } from './lib/isTauri'
|
import { isTauri } from './lib/isTauri'
|
||||||
@ -29,7 +19,7 @@ import {
|
|||||||
isProjectDirectory,
|
isProjectDirectory,
|
||||||
PROJECT_ENTRYPOINT,
|
PROJECT_ENTRYPOINT,
|
||||||
} from './lib/tauriFS'
|
} from './lib/tauriFS'
|
||||||
import { metadata, type Metadata } from 'tauri-plugin-fs-extra-api'
|
import { metadata } from 'tauri-plugin-fs-extra-api'
|
||||||
import DownloadAppBanner from './components/DownloadAppBanner'
|
import DownloadAppBanner from './components/DownloadAppBanner'
|
||||||
import { WasmErrBanner } from './components/WasmErrBanner'
|
import { WasmErrBanner } from './components/WasmErrBanner'
|
||||||
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
||||||
@ -38,82 +28,19 @@ import {
|
|||||||
settingsMachine,
|
settingsMachine,
|
||||||
} from './machines/settingsMachine'
|
} from './machines/settingsMachine'
|
||||||
import { ContextFrom } from 'xstate'
|
import { ContextFrom } from 'xstate'
|
||||||
import CommandBarProvider from 'components/CommandBar/CommandBar'
|
import CommandBarProvider, {
|
||||||
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
CommandBar,
|
||||||
import * as Sentry from '@sentry/react'
|
} from 'components/CommandBar/CommandBar'
|
||||||
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
||||||
import { KclContextProvider, kclManager } from 'lang/KclSinglton'
|
import { KclContextProvider, kclManager } from 'lang/KclSingleton'
|
||||||
import FileMachineProvider from 'components/FileMachineProvider'
|
import FileMachineProvider from 'components/FileMachineProvider'
|
||||||
import { sep } from '@tauri-apps/api/path'
|
import { sep } from '@tauri-apps/api/path'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
if (VITE_KC_SENTRY_DSN && !TEST) {
|
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
|
||||||
Sentry.init({
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const prependRoutes =
|
|
||||||
(routesObject: Record<string, string>) => (prepend: string) => {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(routesObject).map(([constName, path]) => [
|
|
||||||
constName,
|
|
||||||
prepend + path,
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const paths = {
|
|
||||||
INDEX: '/',
|
|
||||||
HOME: '/home',
|
|
||||||
FILE: '/file',
|
|
||||||
SETTINGS: '/settings',
|
|
||||||
SIGN_IN: '/signin',
|
|
||||||
ONBOARDING: prependRoutes(onboardingPaths)(
|
|
||||||
'/onboarding'
|
|
||||||
) as typeof onboardingPaths,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BROWSER_FILE_NAME = 'new'
|
export const BROWSER_FILE_NAME = 'new'
|
||||||
|
|
||||||
export type IndexLoaderData = {
|
|
||||||
code: string | null
|
|
||||||
project?: ProjectWithEntryPointMetadata
|
|
||||||
file?: FileEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProjectWithEntryPointMetadata = FileEntry & {
|
|
||||||
entrypointMetadata: Metadata
|
|
||||||
}
|
|
||||||
export type HomeLoaderData = {
|
|
||||||
projects: ProjectWithEntryPointMetadata[]
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateBrowserRouterArg = Parameters<typeof createBrowserRouter>[0]
|
type CreateBrowserRouterArg = Parameters<typeof createBrowserRouter>[0]
|
||||||
|
|
||||||
const addGlobalContextToElements = (
|
const addGlobalContextToElements = (
|
||||||
@ -145,18 +72,19 @@ const router = createBrowserRouter(
|
|||||||
{
|
{
|
||||||
path: paths.FILE + '/:id',
|
path: paths.FILE + '/:id',
|
||||||
element: (
|
element: (
|
||||||
<Auth>
|
<KclContextProvider>
|
||||||
<FileMachineProvider>
|
<Auth>
|
||||||
<KclContextProvider>
|
<FileMachineProvider>
|
||||||
<ModelingMachineProvider>
|
<ModelingMachineProvider>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<App />
|
<App />
|
||||||
|
<CommandBar />
|
||||||
</ModelingMachineProvider>
|
</ModelingMachineProvider>
|
||||||
<WasmErrBanner />
|
<WasmErrBanner />
|
||||||
</KclContextProvider>
|
</FileMachineProvider>
|
||||||
</FileMachineProvider>
|
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
</Auth>
|
||||||
</Auth>
|
</KclContextProvider>
|
||||||
),
|
),
|
||||||
id: paths.FILE,
|
id: paths.FILE,
|
||||||
loader: async ({
|
loader: async ({
|
||||||
@ -209,6 +137,10 @@ const router = createBrowserRouter(
|
|||||||
const children = await readDir(projectPath, { recursive: true })
|
const children = await readDir(projectPath, { recursive: true })
|
||||||
kclManager.setCodeAndExecute(code, false)
|
kclManager.setCodeAndExecute(code, false)
|
||||||
|
|
||||||
|
// Set the file system manager to the project path
|
||||||
|
// So that WASM gets an updated path for operations
|
||||||
|
fileSystemManager.dir = projectPath
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code,
|
code,
|
||||||
project: {
|
project: {
|
||||||
@ -246,9 +178,10 @@ const router = createBrowserRouter(
|
|||||||
<Auth>
|
<Auth>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<Home />
|
<Home />
|
||||||
|
<CommandBar />
|
||||||
</Auth>
|
</Auth>
|
||||||
),
|
),
|
||||||
loader: async () => {
|
loader: async (): Promise<HomeLoaderData | Response> => {
|
||||||
if (!isTauri()) {
|
if (!isTauri()) {
|
||||||
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
|
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
|
||||||
}
|
}
|
||||||
@ -259,6 +192,7 @@ const router = createBrowserRouter(
|
|||||||
const projectDir = await initializeProjectDirectory(
|
const projectDir = await initializeProjectDirectory(
|
||||||
persistedSettings.defaultDirectory || ''
|
persistedSettings.defaultDirectory || ''
|
||||||
)
|
)
|
||||||
|
let newDefaultDirectory: string | undefined = undefined
|
||||||
if (projectDir !== persistedSettings.defaultDirectory) {
|
if (projectDir !== persistedSettings.defaultDirectory) {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
SETTINGS_PERSIST_KEY,
|
SETTINGS_PERSIST_KEY,
|
||||||
@ -267,6 +201,7 @@ const router = createBrowserRouter(
|
|||||||
defaultDirectory: projectDir,
|
defaultDirectory: projectDir,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
newDefaultDirectory = projectDir
|
||||||
}
|
}
|
||||||
const projectsNoMeta = (await readDir(projectDir)).filter(
|
const projectsNoMeta = (await readDir(projectDir)).filter(
|
||||||
isProjectDirectory
|
isProjectDirectory
|
||||||
@ -282,6 +217,7 @@ const router = createBrowserRouter(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
projects,
|
projects,
|
||||||
|
newDefaultDirectory,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
126
src/Toolbar.tsx
@ -5,6 +5,13 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
import usePlatform from 'hooks/usePlatform'
|
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'
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
@ -13,14 +20,22 @@ export const Toolbar = () => {
|
|||||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||||
const bgClassName =
|
const bgClassName =
|
||||||
'group-enabled:group-hover:bg-energy-10 group-pressed:bg-energy-10 dark:group-enabled:group-hover:bg-chalkboard-80 dark:group-pressed:bg-chalkboard-80'
|
'group-enabled:group-hover:bg-energy-10 group-pressed:bg-energy-10 dark:group-enabled:group-hover:bg-chalkboard-80 dark:group-pressed:bg-chalkboard-80'
|
||||||
const pathId = useMemo(
|
const pathId = useMemo(() => {
|
||||||
() =>
|
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
||||||
isCursorInSketchCommandRange(
|
return false
|
||||||
engineCommandManager.artifactMap,
|
}
|
||||||
context.selectionRanges
|
return isCursorInSketchCommandRange(
|
||||||
),
|
engineCommandManager.artifactMap,
|
||||||
[engineCommandManager.artifactMap, context.selectionRanges]
|
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>) {
|
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
||||||
const span = toolbarButtonsRef.current
|
const span = toolbarButtonsRef.current
|
||||||
@ -50,11 +65,14 @@ export const Toolbar = () => {
|
|||||||
<li className="contents">
|
<li className="contents">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send({ type: 'Enter sketch' })}
|
onClick={() =>
|
||||||
|
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
||||||
|
}
|
||||||
icon={{
|
icon={{
|
||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
bgClassName,
|
bgClassName,
|
||||||
}}
|
}}
|
||||||
|
disabled={disableAllButtons}
|
||||||
>
|
>
|
||||||
<span data-testid="start-sketch">Start Sketch</span>
|
<span data-testid="start-sketch">Start Sketch</span>
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
@ -69,6 +87,7 @@ export const Toolbar = () => {
|
|||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
bgClassName,
|
bgClassName,
|
||||||
}}
|
}}
|
||||||
|
disabled={disableAllButtons}
|
||||||
>
|
>
|
||||||
Edit Sketch
|
Edit Sketch
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
@ -83,50 +102,57 @@ export const Toolbar = () => {
|
|||||||
icon: 'arrowLeft',
|
icon: 'arrowLeft',
|
||||||
bgClassName,
|
bgClassName,
|
||||||
}}
|
}}
|
||||||
|
disabled={disableAllButtons}
|
||||||
>
|
>
|
||||||
Exit Sketch
|
Exit Sketch
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
{state.matches('Sketch') && !state.matches('idle') && (
|
{state.matches('Sketch') && !state.matches('idle') && (
|
||||||
<li className="contents">
|
<>
|
||||||
<ActionButton
|
<li className="contents" key="line-button">
|
||||||
Element="button"
|
<ActionButton
|
||||||
onClick={() =>
|
Element="button"
|
||||||
state.matches('Sketch.Line Tool')
|
onClick={() =>
|
||||||
? send('CancelSketch')
|
state?.matches('Sketch.Line tool')
|
||||||
: send('Equip tool')
|
? send('CancelSketch')
|
||||||
}
|
: send('Equip Line tool')
|
||||||
aria-pressed={state.matches('Sketch.Line Tool')}
|
}
|
||||||
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||||
icon={{
|
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
||||||
icon: 'line',
|
icon={{
|
||||||
bgClassName,
|
icon: 'line',
|
||||||
}}
|
bgClassName,
|
||||||
>
|
}}
|
||||||
Line
|
disabled={disableAllButtons}
|
||||||
</ActionButton>
|
>
|
||||||
</li>
|
Line
|
||||||
)}
|
</ActionButton>
|
||||||
{state.matches('Sketch') && (
|
</li>
|
||||||
<li className="contents">
|
<li className="contents" key="tangential-arc-button">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
state.matches('Sketch.Move Tool')
|
state.matches('Sketch.Tangential arc to')
|
||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send('Equip move tool')
|
: send('Equip tangential arc to')
|
||||||
}
|
}
|
||||||
aria-pressed={state.matches('Sketch.Move Tool')}
|
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||||
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
||||||
icon={{
|
icon={{
|
||||||
icon: 'move',
|
icon: 'arc',
|
||||||
bgClassName,
|
bgClassName,
|
||||||
}}
|
}}
|
||||||
>
|
disabled={
|
||||||
Move
|
(!state.can('Equip tangential arc to') &&
|
||||||
</ActionButton>
|
!state.matches('Sketch.Tangential arc to')) ||
|
||||||
</li>
|
disableAllButtons
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Tangential Arc
|
||||||
|
</ActionButton>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{state.matches('Sketch.SketchIdle') &&
|
{state.matches('Sketch.SketchIdle') &&
|
||||||
state.nextEvents
|
state.nextEvents
|
||||||
@ -151,7 +177,7 @@ export const Toolbar = () => {
|
|||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
.map((eventName) => (
|
.map((eventName) => (
|
||||||
<li className="contents">
|
<li className="contents" key={eventName}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
@ -160,7 +186,7 @@ export const Toolbar = () => {
|
|||||||
disabled={
|
disabled={
|
||||||
!state.nextEvents
|
!state.nextEvents
|
||||||
.filter((event) => state.can(event as any))
|
.filter((event) => state.can(event as any))
|
||||||
.includes(eventName)
|
.includes(eventName) || disableAllButtons
|
||||||
}
|
}
|
||||||
title={eventName}
|
title={eventName}
|
||||||
icon={{
|
icon={{
|
||||||
@ -185,7 +211,7 @@ export const Toolbar = () => {
|
|||||||
data: { name: 'Extrude', ownerMachine: 'modeling' },
|
data: { name: 'Extrude', ownerMachine: 'modeling' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
disabled={!state.can('Extrude')}
|
disabled={!state.can('Extrude') || disableAllButtons}
|
||||||
title={
|
title={
|
||||||
state.can('Extrude')
|
state.can('Extrude')
|
||||||
? 'extrude'
|
? 'extrude'
|
||||||
|
1028
src/clientSideScene/CameraControls.ts
Normal file
250
src/clientSideScene/ClientSideSceneComp.tsx
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import { useRef, useEffect, useState } from 'react'
|
||||||
|
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 { throttle } from 'lib/utils'
|
||||||
|
|
||||||
|
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
||||||
|
const [isCamMoving, setIsCamMoving] = useState(false)
|
||||||
|
const [isTween, setIsTween] = useState(false)
|
||||||
|
|
||||||
|
const { state } = useModelingContext()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sceneInfra.camControls.setIsCamMovingCallback((isMoving, isTween) => {
|
||||||
|
setIsCamMoving(isMoving)
|
||||||
|
setIsTween(isTween)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (DEBUG_SHOW_BOTH_SCENES || !isCamMoving)
|
||||||
|
return { hideClient: false, hideServer: false }
|
||||||
|
let hideServer = state.matches('Sketch')
|
||||||
|
if (isTween) {
|
||||||
|
hideServer = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return { hideClient: !hideServer, hideServer }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ClientSideScene = ({
|
||||||
|
cameraControls,
|
||||||
|
}: {
|
||||||
|
cameraControls: ReturnType<
|
||||||
|
typeof useGlobalStateContext
|
||||||
|
>['settings']['context']['cameraControls']
|
||||||
|
}) => {
|
||||||
|
const canvasRef = useRef<HTMLDivElement>(null)
|
||||||
|
const { state, send } = useModelingContext()
|
||||||
|
const { hideClient, hideServer } = useShouldHideScene()
|
||||||
|
const { setHighlightRange } = useStore((s) => ({
|
||||||
|
setHighlightRange: s.setHighlightRange,
|
||||||
|
highlightRange: s.highlightRange,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Listen for changes to the camera controls setting
|
||||||
|
// and update the client-side scene's controls accordingly.
|
||||||
|
useEffect(() => {
|
||||||
|
sceneInfra.camControls.interactionGuards =
|
||||||
|
cameraMouseDragGuards[cameraControls]
|
||||||
|
}, [cameraControls])
|
||||||
|
useEffect(() => {
|
||||||
|
sceneInfra.updateOtherSelectionColors(
|
||||||
|
state?.context?.selectionRanges?.otherSelections || []
|
||||||
|
)
|
||||||
|
}, [state?.context?.selectionRanges?.otherSelections])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!canvasRef.current) return
|
||||||
|
const canvas = canvasRef.current
|
||||||
|
canvas.appendChild(sceneInfra.renderer.domElement)
|
||||||
|
sceneInfra.animate()
|
||||||
|
sceneInfra.setHighlightCallback(setHighlightRange)
|
||||||
|
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
|
||||||
|
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
|
||||||
|
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)
|
||||||
|
sceneInfra.setSend(send)
|
||||||
|
return () => {
|
||||||
|
canvas?.removeEventListener('mousemove', sceneInfra.onMouseMove)
|
||||||
|
canvas?.removeEventListener('mousedown', sceneInfra.onMouseDown)
|
||||||
|
canvas?.removeEventListener('mouseup', sceneInfra.onMouseUp)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={canvasRef}
|
||||||
|
className={`absolute inset-0 h-full w-full transition-all duration-300 ${
|
||||||
|
hideClient ? 'opacity-0' : 'opacity-100'
|
||||||
|
} ${hideServer ? 'bg-black' : ''} ${
|
||||||
|
!hideClient && !hideServer && state.matches('Sketch')
|
||||||
|
? 'bg-black/80'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const throttled = throttle((a: ReactCameraProperties) => {
|
||||||
|
if (a.type === 'perspective' && a.fov) {
|
||||||
|
sceneInfra.camControls.dollyZoom(a.fov)
|
||||||
|
}
|
||||||
|
}, 1000 / 15)
|
||||||
|
|
||||||
|
export const CamDebugSettings = () => {
|
||||||
|
const [camSettings, setCamSettings] = useState<ReactCameraProperties>({
|
||||||
|
type: 'perspective',
|
||||||
|
fov: 12,
|
||||||
|
position: [0, 0, 0],
|
||||||
|
quaternion: [0, 0, 0, 1],
|
||||||
|
})
|
||||||
|
const [fov, setFov] = useState(12)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings)
|
||||||
|
}, [sceneInfra])
|
||||||
|
useEffect(() => {
|
||||||
|
if (camSettings.type === 'perspective' && camSettings.fov) {
|
||||||
|
setFov(camSettings.fov)
|
||||||
|
}
|
||||||
|
}, [(camSettings as any)?.fov])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>cam settings</h3>
|
||||||
|
perspective cam
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={camSettings.type === 'perspective'}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (camSettings.type === 'perspective') {
|
||||||
|
sceneInfra.camControls.useOrthographicCamera()
|
||||||
|
} else {
|
||||||
|
sceneInfra.camControls.usePerspectiveCamera()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{camSettings.type === 'perspective' && (
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="4"
|
||||||
|
max="90"
|
||||||
|
step={0.5}
|
||||||
|
value={fov}
|
||||||
|
onChange={(e) => {
|
||||||
|
setFov(parseFloat(e.target.value))
|
||||||
|
|
||||||
|
throttled({
|
||||||
|
...camSettings,
|
||||||
|
fov: parseFloat(e.target.value),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className="w-full cursor-pointer pointer-events-auto"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{camSettings.type === 'perspective' && (
|
||||||
|
<div>
|
||||||
|
<span>fov</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={camSettings.fov}
|
||||||
|
className="text-black w-16"
|
||||||
|
onChange={(e) => {
|
||||||
|
sceneInfra.camControls.setCam({
|
||||||
|
...camSettings,
|
||||||
|
fov: parseFloat(e.target.value),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{camSettings.type === 'orthographic' && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<span>fov</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={camSettings.zoom}
|
||||||
|
className="text-black w-16"
|
||||||
|
onChange={(e) => {
|
||||||
|
sceneInfra.camControls.setCam({
|
||||||
|
...camSettings,
|
||||||
|
zoom: parseFloat(e.target.value),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
Position
|
||||||
|
<ul className="flex">
|
||||||
|
<li>
|
||||||
|
<span className="pl-2 pr-1">x:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step={5}
|
||||||
|
data-testid="cam-x-position"
|
||||||
|
value={camSettings.position[0]}
|
||||||
|
className="text-black w-16"
|
||||||
|
onChange={(e) => {
|
||||||
|
sceneInfra.camControls.setCam({
|
||||||
|
...camSettings,
|
||||||
|
position: [
|
||||||
|
parseFloat(e.target.value),
|
||||||
|
camSettings.position[1],
|
||||||
|
camSettings.position[2],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="pl-2 pr-1">y:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step={5}
|
||||||
|
data-testid="cam-y-position"
|
||||||
|
value={camSettings.position[1]}
|
||||||
|
className="text-black w-16"
|
||||||
|
onChange={(e) => {
|
||||||
|
sceneInfra.camControls.setCam({
|
||||||
|
...camSettings,
|
||||||
|
position: [
|
||||||
|
camSettings.position[0],
|
||||||
|
parseFloat(e.target.value),
|
||||||
|
camSettings.position[2],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="pl-2 pr-1">z:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step={5}
|
||||||
|
data-testid="cam-z-position"
|
||||||
|
value={camSettings.position[2]}
|
||||||
|
className="text-black w-16"
|
||||||
|
onChange={(e) => {
|
||||||
|
sceneInfra.camControls.setCam({
|
||||||
|
...camSettings,
|
||||||
|
position: [
|
||||||
|
camSettings.position[0],
|
||||||
|
camSettings.position[1],
|
||||||
|
parseFloat(e.target.value),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
28
src/clientSideScene/helpers.test.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Quaternion } from 'three'
|
||||||
|
import { isQuaternionVertical } from './helpers'
|
||||||
|
|
||||||
|
describe('isQuaternionVertical', () => {
|
||||||
|
it('should identify vertical quaternions', () => {
|
||||||
|
const verticalQuaternions = [
|
||||||
|
new Quaternion(1, 0, 0, 0).normalize(), // bottom
|
||||||
|
new Quaternion(-0.7, 0.7, 0, 0).normalize(), // bottom 2
|
||||||
|
new Quaternion(0, 1, 0, 0).normalize(), // bottom 3
|
||||||
|
new Quaternion(0, 0, 0, 1).normalize(), // look from top
|
||||||
|
]
|
||||||
|
verticalQuaternions.forEach((quaternion) => {
|
||||||
|
expect(isQuaternionVertical(quaternion)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify non-vertical quaternions', () => {
|
||||||
|
const nonVerticalQuaternions = [
|
||||||
|
new Quaternion(0.7, 0, 0, 0.7).normalize(), // front
|
||||||
|
new Quaternion(0, 0.7, 0.7, 0).normalize(), // back
|
||||||
|
new Quaternion(-0.5, 0.5, 0.5, -0.5).normalize(), // left side
|
||||||
|
new Quaternion(0.5, 0.5, 0.5, 0.5).normalize(), // right side
|
||||||
|
]
|
||||||
|
nonVerticalQuaternions.forEach((quaternion) => {
|
||||||
|
expect(isQuaternionVertical(quaternion)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
42
src/clientSideScene/helpers.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
||||||
|
import {
|
||||||
|
GridHelper,
|
||||||
|
LineBasicMaterial,
|
||||||
|
OrthographicCamera,
|
||||||
|
PerspectiveCamera,
|
||||||
|
Group,
|
||||||
|
Mesh,
|
||||||
|
Quaternion,
|
||||||
|
Vector3,
|
||||||
|
} from 'three'
|
||||||
|
|
||||||
|
export function createGridHelper({
|
||||||
|
size,
|
||||||
|
divisions,
|
||||||
|
}: {
|
||||||
|
size: number
|
||||||
|
divisions: number
|
||||||
|
}) {
|
||||||
|
const gridHelperMaterial = new LineBasicMaterial({
|
||||||
|
color: 0xaaaaaa,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
depthTest: false,
|
||||||
|
})
|
||||||
|
const gridHelper = new GridHelper(size, divisions, 0x0000ff, 0xffffff)
|
||||||
|
gridHelper.material = gridHelperMaterial
|
||||||
|
gridHelper.rotation.x = Math.PI / 2
|
||||||
|
return gridHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) =>
|
||||||
|
0.55 / cam.zoom
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
1140
src/clientSideScene/sceneEntities.ts
Normal file
619
src/clientSideScene/sceneInfra.ts
Normal file
@ -0,0 +1,619 @@
|
|||||||
|
import {
|
||||||
|
AmbientLight,
|
||||||
|
Color,
|
||||||
|
GridHelper,
|
||||||
|
LineBasicMaterial,
|
||||||
|
OrthographicCamera,
|
||||||
|
PerspectiveCamera,
|
||||||
|
Scene,
|
||||||
|
Vector3,
|
||||||
|
WebGLRenderer,
|
||||||
|
Raycaster,
|
||||||
|
Vector2,
|
||||||
|
Group,
|
||||||
|
PlaneGeometry,
|
||||||
|
MeshBasicMaterial,
|
||||||
|
Mesh,
|
||||||
|
DoubleSide,
|
||||||
|
Intersection,
|
||||||
|
Object3D,
|
||||||
|
Object3DEventMap,
|
||||||
|
} from 'three'
|
||||||
|
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import * as TWEEN from '@tweenjs/tween.js'
|
||||||
|
import { SourceRange } from 'lang/wasm'
|
||||||
|
import { Axis } from 'lib/selections'
|
||||||
|
import { BaseUnit, SETTINGS_PERSIST_KEY } from 'machines/settingsMachine'
|
||||||
|
import { CameraControls } from './CameraControls'
|
||||||
|
|
||||||
|
type SendType = ReturnType<typeof useModelingContext>['send']
|
||||||
|
|
||||||
|
// 63.5 is definitely a bit of a magic number, play with it until it looked right
|
||||||
|
// if it were 64, that would feel like it's something in the engine where a random
|
||||||
|
// power of 2 is used, but it's the 0.5 seems to make things look much more correct
|
||||||
|
export const ZOOM_MAGIC_NUMBER = 63.5
|
||||||
|
|
||||||
|
export const INTERSECTION_PLANE_LAYER = 1
|
||||||
|
export const SKETCH_LAYER = 2
|
||||||
|
export const DEBUG_SHOW_INTERSECTION_PLANE = false
|
||||||
|
export const DEBUG_SHOW_BOTH_SCENES = false
|
||||||
|
|
||||||
|
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
||||||
|
export const DEFAULT_PLANES = 'default-planes'
|
||||||
|
|
||||||
|
export const X_AXIS = 'xAxis'
|
||||||
|
export const Y_AXIS = 'yAxis'
|
||||||
|
export const AXIS_GROUP = 'axisGroup'
|
||||||
|
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
|
||||||
|
export const ARROWHEAD = 'arrowhead'
|
||||||
|
|
||||||
|
export interface OnMouseEnterLeaveArgs {
|
||||||
|
selected: Object3D<Object3DEventMap>
|
||||||
|
mouseEvent: MouseEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
|
||||||
|
intersectionPoint: {
|
||||||
|
twoD: Vector2
|
||||||
|
threeD: Vector3
|
||||||
|
}
|
||||||
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
|
}
|
||||||
|
interface OnClickCallbackArgs {
|
||||||
|
mouseEvent: MouseEvent
|
||||||
|
intersectionPoint?: {
|
||||||
|
twoD: Vector2
|
||||||
|
threeD: Vector3
|
||||||
|
}
|
||||||
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
|
selected?: Object3D<Object3DEventMap>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnMoveCallbackArgs {
|
||||||
|
mouseEvent: MouseEvent
|
||||||
|
intersectionPoint: {
|
||||||
|
twoD: Vector2
|
||||||
|
threeD: Vector3
|
||||||
|
}
|
||||||
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
|
selected?: Object3D<Object3DEventMap>
|
||||||
|
}
|
||||||
|
|
||||||
|
// This singleton class is responsible for all of the under the hood setup for the client side scene.
|
||||||
|
// That is the cameras and switching between them, raycasters for click mouse events and their abstractions (onClick etc), setting up controls.
|
||||||
|
// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts
|
||||||
|
class SceneInfra {
|
||||||
|
static instance: SceneInfra
|
||||||
|
scene: Scene
|
||||||
|
renderer: WebGLRenderer
|
||||||
|
camControls: CameraControls
|
||||||
|
isPerspective = true
|
||||||
|
fov = 45
|
||||||
|
fovBeforeAnimate = 45
|
||||||
|
isFovAnimationInProgress = false
|
||||||
|
_baseUnit: BaseUnit = 'mm'
|
||||||
|
_baseUnitMultiplier = 1
|
||||||
|
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
|
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
|
||||||
|
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
|
||||||
|
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||||
|
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||||
|
setCallbacks = (callbacks: {
|
||||||
|
onDrag?: (arg: OnDragCallbackArgs) => void
|
||||||
|
onMove?: (arg: OnMoveCallbackArgs) => void
|
||||||
|
onClick?: (arg?: OnClickCallbackArgs) => void
|
||||||
|
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
|
||||||
|
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
|
||||||
|
}) => {
|
||||||
|
this.onDragCallback = callbacks.onDrag || this.onDragCallback
|
||||||
|
this.onMoveCallback = callbacks.onMove || this.onMoveCallback
|
||||||
|
this.onClickCallback = callbacks.onClick || this.onClickCallback
|
||||||
|
this.onMouseEnter = callbacks.onMouseEnter || this.onMouseEnter
|
||||||
|
this.onMouseLeave = callbacks.onMouseLeave || this.onMouseLeave
|
||||||
|
this.selected = null // following selections between callbacks being set is too tricky
|
||||||
|
}
|
||||||
|
set baseUnit(unit: BaseUnit) {
|
||||||
|
this._baseUnit = unit
|
||||||
|
this._baseUnitMultiplier = baseUnitTomm(unit)
|
||||||
|
this.scene.scale.set(
|
||||||
|
this._baseUnitMultiplier,
|
||||||
|
this._baseUnitMultiplier,
|
||||||
|
this._baseUnitMultiplier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
resetMouseListeners = () => {
|
||||||
|
sceneInfra.setCallbacks({
|
||||||
|
onDrag: () => {},
|
||||||
|
onMove: () => {},
|
||||||
|
onClick: () => {},
|
||||||
|
onMouseEnter: () => {},
|
||||||
|
onMouseLeave: () => {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
highlightCallback: (a: SourceRange) => void = () => {}
|
||||||
|
setHighlightCallback(cb: (a: SourceRange) => void) {
|
||||||
|
this.highlightCallback = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
modelingSend: SendType = (() => {}) as any
|
||||||
|
setSend(send: SendType) {
|
||||||
|
this.modelingSend = send
|
||||||
|
}
|
||||||
|
|
||||||
|
hoveredObject: null | any = null
|
||||||
|
raycaster = new Raycaster()
|
||||||
|
planeRaycaster = new Raycaster()
|
||||||
|
currentMouseVector = new Vector2()
|
||||||
|
selected: {
|
||||||
|
mouseDownVector: Vector2
|
||||||
|
object: Object3D<Object3DEventMap>
|
||||||
|
hasBeenDragged: boolean
|
||||||
|
} | null = null
|
||||||
|
mouseDownVector: null | Vector2 = null
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// SCENE
|
||||||
|
this.scene = new Scene()
|
||||||
|
this.scene.background = new Color(0x000000)
|
||||||
|
this.scene.background = null
|
||||||
|
|
||||||
|
// RENDERER
|
||||||
|
this.renderer = new WebGLRenderer({ antialias: true, alpha: true }) // Enable transparency
|
||||||
|
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
this.renderer.setClearColor(0x000000, 0) // Set clear color to black with 0 alpha (fully transparent)
|
||||||
|
window.addEventListener('resize', this.onWindowResize)
|
||||||
|
|
||||||
|
// CAMERA
|
||||||
|
const camHeightDistanceRatio = 0.5
|
||||||
|
const baseUnit: BaseUnit =
|
||||||
|
JSON.parse(localStorage?.getItem(SETTINGS_PERSIST_KEY) || ('{}' as any))
|
||||||
|
.baseUnit || 'mm'
|
||||||
|
const baseRadius = 5.6
|
||||||
|
const length = baseUnitTomm(baseUnit) * baseRadius
|
||||||
|
const ang = Math.atan(camHeightDistanceRatio)
|
||||||
|
const x = Math.cos(ang) * length
|
||||||
|
const y = Math.sin(ang) * length
|
||||||
|
|
||||||
|
this.camControls = new CameraControls(false, this.renderer.domElement)
|
||||||
|
this.camControls.subscribeToCamChange(() => this.onCameraChange())
|
||||||
|
this.camControls.camera.layers.enable(SKETCH_LAYER)
|
||||||
|
this.camControls.camera.position.set(0, -x, y)
|
||||||
|
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||||
|
this.camControls.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||||
|
|
||||||
|
// RAYCASTERS
|
||||||
|
this.raycaster.layers.enable(SKETCH_LAYER)
|
||||||
|
this.raycaster.layers.disable(0)
|
||||||
|
this.planeRaycaster.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||||
|
|
||||||
|
// GRID
|
||||||
|
const size = 100
|
||||||
|
const divisions = 10
|
||||||
|
const gridHelperMaterial = new LineBasicMaterial({
|
||||||
|
color: 0x0000ff,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
})
|
||||||
|
|
||||||
|
const gridHelper = new GridHelper(size, divisions, 0x0000ff, 0xffffff)
|
||||||
|
gridHelper.material = gridHelperMaterial
|
||||||
|
gridHelper.rotation.x = Math.PI / 2
|
||||||
|
// this.scene.add(gridHelper) // more of a debug thing, but maybe useful
|
||||||
|
|
||||||
|
const light = new AmbientLight(0x505050) // soft white light
|
||||||
|
this.scene.add(light)
|
||||||
|
|
||||||
|
SceneInfra.instance = this
|
||||||
|
}
|
||||||
|
|
||||||
|
onCameraChange = () => {
|
||||||
|
const scale = getSceneScale(
|
||||||
|
this.camControls.camera,
|
||||||
|
this.camControls.target
|
||||||
|
)
|
||||||
|
const planesGroup = this.scene.getObjectByName(DEFAULT_PLANES)
|
||||||
|
const axisGroup = this.scene
|
||||||
|
.getObjectByName(AXIS_GROUP)
|
||||||
|
?.getObjectByName('gridHelper')
|
||||||
|
planesGroup &&
|
||||||
|
planesGroup.scale.set(
|
||||||
|
scale / sceneInfra._baseUnitMultiplier,
|
||||||
|
scale / sceneInfra._baseUnitMultiplier,
|
||||||
|
scale / sceneInfra._baseUnitMultiplier
|
||||||
|
)
|
||||||
|
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowResize = () => {
|
||||||
|
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
animate = () => {
|
||||||
|
requestAnimationFrame(this.animate)
|
||||||
|
TWEEN.update() // This will update all tweens during the animation loop
|
||||||
|
if (!this.isFovAnimationInProgress) {
|
||||||
|
// console.log('animation frame', this.cameraControls.camera)
|
||||||
|
this.camControls.update()
|
||||||
|
this.renderer.render(this.scene, this.camControls.camera)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose = () => {
|
||||||
|
// Dispose of scene resources, renderer, and controls
|
||||||
|
this.renderer.dispose()
|
||||||
|
window.removeEventListener('resize', this.onWindowResize)
|
||||||
|
// Dispose of any other resources like geometries, materials, textures
|
||||||
|
}
|
||||||
|
getPlaneIntersectPoint = (): {
|
||||||
|
twoD?: Vector2
|
||||||
|
threeD?: Vector3
|
||||||
|
intersection: Intersection<Object3D<Object3DEventMap>>
|
||||||
|
} | null => {
|
||||||
|
this.planeRaycaster.setFromCamera(
|
||||||
|
this.currentMouseVector,
|
||||||
|
sceneInfra.camControls.camera
|
||||||
|
)
|
||||||
|
const planeIntersects = this.planeRaycaster.intersectObjects(
|
||||||
|
this.scene.children,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
const recastablePlaneIntersect = planeIntersects.find(
|
||||||
|
(intersect) => intersect.object.name === RAYCASTABLE_PLANE
|
||||||
|
)
|
||||||
|
if (!planeIntersects.length) return null
|
||||||
|
if (!recastablePlaneIntersect) return { intersection: planeIntersects[0] }
|
||||||
|
const planePosition = planeIntersects[0].object.position
|
||||||
|
const inversePlaneQuaternion = planeIntersects[0].object.quaternion
|
||||||
|
.clone()
|
||||||
|
.invert()
|
||||||
|
const intersectPoint = planeIntersects[0].point
|
||||||
|
let transformedPoint = intersectPoint.clone()
|
||||||
|
if (transformedPoint) {
|
||||||
|
transformedPoint.applyQuaternion(inversePlaneQuaternion)
|
||||||
|
transformedPoint?.sub(
|
||||||
|
new Vector3(...planePosition).applyQuaternion(inversePlaneQuaternion)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
twoD: new Vector2(
|
||||||
|
transformedPoint.x / this._baseUnitMultiplier,
|
||||||
|
transformedPoint.y / this._baseUnitMultiplier
|
||||||
|
), // z should be 0
|
||||||
|
threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
|
||||||
|
intersection: planeIntersects[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMouseMove = (mouseEvent: MouseEvent) => {
|
||||||
|
this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
|
||||||
|
this.currentMouseVector.y =
|
||||||
|
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
|
||||||
|
|
||||||
|
const planeIntersectPoint = this.getPlaneIntersectPoint()
|
||||||
|
const intersects = this.raycastRing()
|
||||||
|
|
||||||
|
if (this.selected) {
|
||||||
|
const hasBeenDragged = !compareVec2Epsilon2(
|
||||||
|
[this.currentMouseVector.x, this.currentMouseVector.y],
|
||||||
|
[this.selected.mouseDownVector.x, this.selected.mouseDownVector.y],
|
||||||
|
0.02
|
||||||
|
)
|
||||||
|
if (!this.selected.hasBeenDragged && hasBeenDragged) {
|
||||||
|
this.selected.hasBeenDragged = true
|
||||||
|
// this is where we could fire a onDragStart event
|
||||||
|
// console.log('onDragStart', this.selected)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
hasBeenDragged &&
|
||||||
|
planeIntersectPoint &&
|
||||||
|
planeIntersectPoint.twoD &&
|
||||||
|
planeIntersectPoint.threeD
|
||||||
|
) {
|
||||||
|
// // console.log('onDrag', this.selected)
|
||||||
|
|
||||||
|
this.onDragCallback({
|
||||||
|
mouseEvent,
|
||||||
|
intersectionPoint: {
|
||||||
|
twoD: planeIntersectPoint.twoD,
|
||||||
|
threeD: planeIntersectPoint.threeD,
|
||||||
|
},
|
||||||
|
intersects,
|
||||||
|
selected: this.selected.object,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
planeIntersectPoint &&
|
||||||
|
planeIntersectPoint.twoD &&
|
||||||
|
planeIntersectPoint.threeD
|
||||||
|
) {
|
||||||
|
this.onMoveCallback({
|
||||||
|
mouseEvent,
|
||||||
|
intersectionPoint: {
|
||||||
|
twoD: planeIntersectPoint.twoD,
|
||||||
|
threeD: planeIntersectPoint.threeD,
|
||||||
|
},
|
||||||
|
intersects,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersects[0]) {
|
||||||
|
const firstIntersectObject = intersects[0].object
|
||||||
|
if (this.hoveredObject !== firstIntersectObject) {
|
||||||
|
if (this.hoveredObject) {
|
||||||
|
this.onMouseLeave({
|
||||||
|
selected: this.hoveredObject,
|
||||||
|
mouseEvent: mouseEvent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.hoveredObject = firstIntersectObject
|
||||||
|
this.onMouseEnter({
|
||||||
|
selected: this.hoveredObject,
|
||||||
|
mouseEvent: mouseEvent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.hoveredObject) {
|
||||||
|
this.onMouseLeave({
|
||||||
|
selected: this.hoveredObject,
|
||||||
|
mouseEvent: mouseEvent,
|
||||||
|
})
|
||||||
|
this.hoveredObject = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
raycastRing = (
|
||||||
|
pixelRadius = 8,
|
||||||
|
rayRingCount = 32
|
||||||
|
): Intersection<Object3D<Object3DEventMap>>[] => {
|
||||||
|
const mouseDownVector = this.currentMouseVector.clone()
|
||||||
|
const intersectionsMap = new Map<
|
||||||
|
Object3D,
|
||||||
|
Intersection<Object3D<Object3DEventMap>>
|
||||||
|
>()
|
||||||
|
|
||||||
|
const updateIntersectionsMap = (
|
||||||
|
intersections: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
|
) => {
|
||||||
|
intersections.forEach((intersection) => {
|
||||||
|
if (intersection.object.type !== 'GridHelper') {
|
||||||
|
const existingIntersection = intersectionsMap.get(intersection.object)
|
||||||
|
if (
|
||||||
|
!existingIntersection ||
|
||||||
|
existingIntersection.distance > intersection.distance
|
||||||
|
) {
|
||||||
|
intersectionsMap.set(intersection.object, intersection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the center point
|
||||||
|
this.raycaster.setFromCamera(mouseDownVector, this.camControls.camera)
|
||||||
|
updateIntersectionsMap(
|
||||||
|
this.raycaster.intersectObjects(this.scene.children, true)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check the ring points
|
||||||
|
for (let i = 0; i < rayRingCount; i++) {
|
||||||
|
const angle = (i / rayRingCount) * Math.PI * 2
|
||||||
|
const offsetX = ((pixelRadius * Math.cos(angle)) / window.innerWidth) * 2
|
||||||
|
const offsetY = ((pixelRadius * Math.sin(angle)) / window.innerHeight) * 2
|
||||||
|
const ringVector = new Vector2(
|
||||||
|
mouseDownVector.x + offsetX,
|
||||||
|
mouseDownVector.y - offsetY
|
||||||
|
)
|
||||||
|
this.raycaster.setFromCamera(ringVector, this.camControls.camera)
|
||||||
|
updateIntersectionsMap(
|
||||||
|
this.raycaster.intersectObjects(this.scene.children, true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the map values to an array and sort by distance
|
||||||
|
return Array.from(intersectionsMap.values()).sort(
|
||||||
|
(a, b) => a.distance - b.distance
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseDown = (event: MouseEvent) => {
|
||||||
|
this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1
|
||||||
|
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
|
||||||
|
|
||||||
|
const mouseDownVector = this.currentMouseVector.clone()
|
||||||
|
const intersect = this.raycastRing()[0]
|
||||||
|
|
||||||
|
if (intersect) {
|
||||||
|
const intersectParent = intersect?.object?.parent as Group
|
||||||
|
this.selected = intersectParent.isGroup
|
||||||
|
? {
|
||||||
|
mouseDownVector,
|
||||||
|
object: intersect.object,
|
||||||
|
hasBeenDragged: false,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseUp = (mouseEvent: MouseEvent) => {
|
||||||
|
this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
|
||||||
|
this.currentMouseVector.y =
|
||||||
|
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
|
||||||
|
const planeIntersectPoint = this.getPlaneIntersectPoint()
|
||||||
|
const intersects = this.raycastRing()
|
||||||
|
|
||||||
|
if (this.selected) {
|
||||||
|
if (this.selected.hasBeenDragged) {
|
||||||
|
// this is where we could fire a onDragEnd event
|
||||||
|
// console.log('onDragEnd', this.selected)
|
||||||
|
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
|
||||||
|
// fire onClick event as there was no drags
|
||||||
|
this.onClickCallback({
|
||||||
|
mouseEvent,
|
||||||
|
intersectionPoint: {
|
||||||
|
twoD: planeIntersectPoint.twoD,
|
||||||
|
threeD: planeIntersectPoint.threeD,
|
||||||
|
},
|
||||||
|
intersects,
|
||||||
|
selected: this.selected.object,
|
||||||
|
})
|
||||||
|
} else if (planeIntersectPoint) {
|
||||||
|
this.onClickCallback({
|
||||||
|
mouseEvent,
|
||||||
|
intersects,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.onClickCallback()
|
||||||
|
}
|
||||||
|
// Clear the selected state whether it was dragged or not
|
||||||
|
this.selected = null
|
||||||
|
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
|
||||||
|
this.onClickCallback({
|
||||||
|
mouseEvent,
|
||||||
|
intersectionPoint: {
|
||||||
|
twoD: planeIntersectPoint.twoD,
|
||||||
|
threeD: planeIntersectPoint.threeD,
|
||||||
|
},
|
||||||
|
intersects,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.onClickCallback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showDefaultPlanes() {
|
||||||
|
const addPlane = (
|
||||||
|
rotation: { x: number; y: number; z: number }, //
|
||||||
|
type: DefaultPlane
|
||||||
|
): Mesh => {
|
||||||
|
const planeGeometry = new PlaneGeometry(100, 100)
|
||||||
|
const planeMaterial = new MeshBasicMaterial({
|
||||||
|
color: defaultPlaneColor(type),
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.0,
|
||||||
|
side: DoubleSide,
|
||||||
|
depthTest: false, // needed to avoid transparency issues
|
||||||
|
})
|
||||||
|
const plane = new Mesh(planeGeometry, planeMaterial)
|
||||||
|
plane.rotation.x = rotation.x
|
||||||
|
plane.rotation.y = rotation.y
|
||||||
|
plane.rotation.z = rotation.z
|
||||||
|
plane.userData.type = type
|
||||||
|
plane.name = type
|
||||||
|
return plane
|
||||||
|
}
|
||||||
|
const planes = [
|
||||||
|
addPlane({ x: 0, y: Math.PI / 2, z: 0 }, YZ_PLANE),
|
||||||
|
addPlane({ x: 0, y: 0, z: 0 }, XY_PLANE),
|
||||||
|
addPlane({ x: -Math.PI / 2, y: 0, z: 0 }, XZ_PLANE),
|
||||||
|
]
|
||||||
|
const planesGroup = new Group()
|
||||||
|
planesGroup.userData.type = DEFAULT_PLANES
|
||||||
|
planesGroup.name = DEFAULT_PLANES
|
||||||
|
planesGroup.add(...planes)
|
||||||
|
planesGroup.traverse((child) => {
|
||||||
|
if (child instanceof Mesh) {
|
||||||
|
child.layers.enable(SKETCH_LAYER)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
planesGroup.layers.enable(SKETCH_LAYER)
|
||||||
|
const sceneScale = getSceneScale(
|
||||||
|
this.camControls.camera,
|
||||||
|
this.camControls.target
|
||||||
|
)
|
||||||
|
planesGroup.scale.set(
|
||||||
|
sceneScale / sceneInfra._baseUnitMultiplier,
|
||||||
|
sceneScale / sceneInfra._baseUnitMultiplier,
|
||||||
|
sceneScale / sceneInfra._baseUnitMultiplier
|
||||||
|
)
|
||||||
|
this.scene.add(planesGroup)
|
||||||
|
}
|
||||||
|
removeDefaultPlanes() {
|
||||||
|
const planesGroup = this.scene.children.find(
|
||||||
|
({ userData }) => userData.type === DEFAULT_PLANES
|
||||||
|
)
|
||||||
|
if (planesGroup) this.scene.remove(planesGroup)
|
||||||
|
}
|
||||||
|
updateOtherSelectionColors = (otherSelections: Axis[]) => {
|
||||||
|
const axisGroup = sceneInfra.scene.children.find(
|
||||||
|
({ userData }) => userData?.type === AXIS_GROUP
|
||||||
|
)
|
||||||
|
const axisMap: { [key: string]: Axis } = {
|
||||||
|
[X_AXIS]: 'x-axis',
|
||||||
|
[Y_AXIS]: 'y-axis',
|
||||||
|
}
|
||||||
|
axisGroup?.children.forEach((_mesh) => {
|
||||||
|
const mesh = _mesh as Mesh
|
||||||
|
const mat = mesh.material as MeshBasicMaterial
|
||||||
|
if (otherSelections.includes(axisMap[mesh.userData?.type])) {
|
||||||
|
mat.color.set(mesh?.userData?.baseColor)
|
||||||
|
mat.color.offsetHSL(0, 0, 0.2)
|
||||||
|
mesh.userData.isSelected = true
|
||||||
|
} else {
|
||||||
|
mat.color.set(mesh?.userData?.baseColor)
|
||||||
|
mesh.userData.isSelected = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sceneInfra = new SceneInfra()
|
||||||
|
|
||||||
|
export function getSceneScale(
|
||||||
|
camera: PerspectiveCamera | OrthographicCamera,
|
||||||
|
target: Vector3
|
||||||
|
): number {
|
||||||
|
const distance =
|
||||||
|
camera instanceof PerspectiveCamera
|
||||||
|
? camera.position.distanceTo(target)
|
||||||
|
: 63.7942123 / camera.zoom
|
||||||
|
|
||||||
|
if (distance <= 20) return 0.1
|
||||||
|
else if (distance > 20 && distance <= 200) return 1
|
||||||
|
else if (distance > 200 && distance <= 2000) return 10
|
||||||
|
else if (distance > 2000 && distance <= 20000) return 100
|
||||||
|
else if (distance > 20000) return 1000
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseUnitTomm(baseUnit: BaseUnit) {
|
||||||
|
switch (baseUnit) {
|
||||||
|
case 'mm':
|
||||||
|
return 1
|
||||||
|
case 'cm':
|
||||||
|
return 10
|
||||||
|
case 'm':
|
||||||
|
return 1000
|
||||||
|
case 'in':
|
||||||
|
return 25.4
|
||||||
|
case 'ft':
|
||||||
|
return 304.8
|
||||||
|
case 'yd':
|
||||||
|
return 914.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DefaultPlane =
|
||||||
|
| 'xy-default-plane'
|
||||||
|
| 'xz-default-plane'
|
||||||
|
| 'yz-default-plane'
|
||||||
|
|
||||||
|
export const XY_PLANE: DefaultPlane = 'xy-default-plane'
|
||||||
|
export const XZ_PLANE: DefaultPlane = 'xz-default-plane'
|
||||||
|
export const YZ_PLANE: DefaultPlane = 'yz-default-plane'
|
||||||
|
|
||||||
|
export function defaultPlaneColor(
|
||||||
|
plane: DefaultPlane,
|
||||||
|
lowCh = 0.1,
|
||||||
|
highCh = 0.7
|
||||||
|
): Color {
|
||||||
|
switch (plane) {
|
||||||
|
case XY_PLANE:
|
||||||
|
return new Color(highCh, lowCh, lowCh)
|
||||||
|
case XZ_PLANE:
|
||||||
|
return new Color(lowCh, lowCh, highCh)
|
||||||
|
case YZ_PLANE:
|
||||||
|
return new Color(lowCh, highCh, lowCh)
|
||||||
|
}
|
||||||
|
return new Color(lowCh, lowCh, lowCh)
|
||||||
|
}
|
400
src/clientSideScene/segments.ts
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
|
import {
|
||||||
|
BoxGeometry,
|
||||||
|
BufferGeometry,
|
||||||
|
CatmullRomCurve3,
|
||||||
|
ConeGeometry,
|
||||||
|
CurvePath,
|
||||||
|
EllipseCurve,
|
||||||
|
ExtrudeGeometry,
|
||||||
|
Group,
|
||||||
|
LineCurve3,
|
||||||
|
Mesh,
|
||||||
|
MeshBasicMaterial,
|
||||||
|
NormalBufferAttributes,
|
||||||
|
Shape,
|
||||||
|
SphereGeometry,
|
||||||
|
Vector2,
|
||||||
|
Vector3,
|
||||||
|
} from 'three'
|
||||||
|
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,
|
||||||
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
|
TANGENTIAL_ARC_TO_SEGMENT_BODY,
|
||||||
|
TANGENTIAL_ARC_TO__SEGMENT_DASH,
|
||||||
|
} from './sceneEntities'
|
||||||
|
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,
|
||||||
|
id,
|
||||||
|
pathToNode,
|
||||||
|
isDraftSegment,
|
||||||
|
scale = 1,
|
||||||
|
callExpName,
|
||||||
|
}: {
|
||||||
|
from: Coords2d
|
||||||
|
to: Coords2d
|
||||||
|
id: string
|
||||||
|
pathToNode: PathToNode
|
||||||
|
isDraftSegment?: boolean
|
||||||
|
scale?: number
|
||||||
|
callExpName: string
|
||||||
|
}): Group {
|
||||||
|
const group = new Group()
|
||||||
|
|
||||||
|
const shape = new Shape()
|
||||||
|
shape.moveTo(0, -0.08 * scale)
|
||||||
|
shape.lineTo(0, 0.08 * scale) // The width of the line
|
||||||
|
|
||||||
|
let geometry
|
||||||
|
if (isDraftSegment) {
|
||||||
|
geometry = dashedStraight(from, to, shape, scale)
|
||||||
|
} else {
|
||||||
|
const line = new LineCurve3(
|
||||||
|
new Vector3(from[0], from[1], 0),
|
||||||
|
new Vector3(to[0], to[1], 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
geometry = new ExtrudeGeometry(shape, {
|
||||||
|
steps: 2,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: line,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseColor = callExpName === 'close' ? 0x444444 : 0xffffff
|
||||||
|
const body = new MeshBasicMaterial({ color: baseColor })
|
||||||
|
const mesh = new Mesh(geometry, body)
|
||||||
|
mesh.userData.type = isDraftSegment
|
||||||
|
? STRAIGHT_SEGMENT_DASH
|
||||||
|
: STRAIGHT_SEGMENT_BODY
|
||||||
|
mesh.name = STRAIGHT_SEGMENT_BODY
|
||||||
|
|
||||||
|
group.userData = {
|
||||||
|
type: STRAIGHT_SEGMENT,
|
||||||
|
id,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
pathToNode,
|
||||||
|
isSelected: false,
|
||||||
|
callExpName,
|
||||||
|
baseColor,
|
||||||
|
}
|
||||||
|
group.name = STRAIGHT_SEGMENT
|
||||||
|
|
||||||
|
const arrowGroup = createArrowhead(scale)
|
||||||
|
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)
|
||||||
|
|
||||||
|
group.add(mesh)
|
||||||
|
if (callExpName !== 'close') group.add(arrowGroup)
|
||||||
|
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
function createArrowhead(scale = 1): Group {
|
||||||
|
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
|
||||||
|
const arrowheadMesh = new Mesh(new ConeGeometry(0.31, 1.5, 12), arrowMaterial)
|
||||||
|
arrowheadMesh.position.set(0, -0.6, 0)
|
||||||
|
const sphereMesh = new Mesh(new SphereGeometry(0.27, 12, 12), arrowMaterial)
|
||||||
|
|
||||||
|
const arrowGroup = new Group()
|
||||||
|
arrowGroup.userData.type = ARROWHEAD
|
||||||
|
arrowGroup.name = ARROWHEAD
|
||||||
|
arrowGroup.add(arrowheadMesh, sphereMesh)
|
||||||
|
arrowGroup.lookAt(new Vector3(0, 1, 0))
|
||||||
|
arrowGroup.scale.set(scale, scale, scale)
|
||||||
|
return arrowGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tangentialArcToSegment({
|
||||||
|
prevSegment,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
id,
|
||||||
|
pathToNode,
|
||||||
|
isDraftSegment,
|
||||||
|
scale = 1,
|
||||||
|
}: {
|
||||||
|
prevSegment: SketchGroup['value'][number]
|
||||||
|
from: Coords2d
|
||||||
|
to: Coords2d
|
||||||
|
id: string
|
||||||
|
pathToNode: PathToNode
|
||||||
|
isDraftSegment?: boolean
|
||||||
|
scale?: number
|
||||||
|
}): Group {
|
||||||
|
const group = new Group()
|
||||||
|
|
||||||
|
const previousPoint =
|
||||||
|
prevSegment?.type === 'TangentialArcTo'
|
||||||
|
? getTangentPointFromPreviousArc(
|
||||||
|
prevSegment.center,
|
||||||
|
prevSegment.ccw,
|
||||||
|
prevSegment.to
|
||||||
|
)
|
||||||
|
: prevSegment.from
|
||||||
|
|
||||||
|
const { center, radius, startAngle, endAngle, ccw } = getTangentialArcToInfo({
|
||||||
|
arcStartPoint: from,
|
||||||
|
arcEndPoint: to,
|
||||||
|
tanPreviousPoint: previousPoint,
|
||||||
|
obtuse: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const geometry = createArcGeometry({
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
startAngle,
|
||||||
|
endAngle,
|
||||||
|
ccw,
|
||||||
|
isDashed: isDraftSegment,
|
||||||
|
scale,
|
||||||
|
})
|
||||||
|
|
||||||
|
const body = new MeshBasicMaterial({ color: 0xffffff })
|
||||||
|
const mesh = new Mesh(geometry, body)
|
||||||
|
mesh.userData.type = isDraftSegment
|
||||||
|
? TANGENTIAL_ARC_TO__SEGMENT_DASH
|
||||||
|
: TANGENTIAL_ARC_TO_SEGMENT_BODY
|
||||||
|
|
||||||
|
group.userData = {
|
||||||
|
type: TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
|
id,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
prevSegment,
|
||||||
|
pathToNode,
|
||||||
|
isSelected: false,
|
||||||
|
}
|
||||||
|
group.name = TANGENTIAL_ARC_TO_SEGMENT
|
||||||
|
|
||||||
|
const arrowGroup = createArrowhead(scale)
|
||||||
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
|
const arrowheadAngle = endAngle + (Math.PI / 2) * (ccw ? 1 : -1)
|
||||||
|
arrowGroup.quaternion.setFromUnitVectors(
|
||||||
|
new Vector3(0, 1, 0),
|
||||||
|
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
group.add(mesh, arrowGroup)
|
||||||
|
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createArcGeometry({
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
startAngle,
|
||||||
|
endAngle,
|
||||||
|
ccw,
|
||||||
|
isDashed = false,
|
||||||
|
scale = 1,
|
||||||
|
}: {
|
||||||
|
center: Coords2d
|
||||||
|
radius: number
|
||||||
|
startAngle: number
|
||||||
|
endAngle: number
|
||||||
|
ccw: boolean
|
||||||
|
isDashed?: boolean
|
||||||
|
scale?: number
|
||||||
|
}): BufferGeometry {
|
||||||
|
const dashSize = 1.2 * scale
|
||||||
|
const gapSize = 1.2 * scale
|
||||||
|
const arcStart = new EllipseCurve(
|
||||||
|
center[0],
|
||||||
|
center[1],
|
||||||
|
radius,
|
||||||
|
radius,
|
||||||
|
startAngle,
|
||||||
|
endAngle,
|
||||||
|
!ccw,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
const arcEnd = new EllipseCurve(
|
||||||
|
center[0],
|
||||||
|
center[1],
|
||||||
|
radius,
|
||||||
|
radius,
|
||||||
|
endAngle,
|
||||||
|
startAngle,
|
||||||
|
ccw,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
const shape = new Shape()
|
||||||
|
shape.moveTo(0, -0.08 * scale)
|
||||||
|
shape.lineTo(0, 0.08 * scale) // The width of the line
|
||||||
|
|
||||||
|
if (!isDashed) {
|
||||||
|
const points = arcStart.getPoints(50)
|
||||||
|
const path = new CurvePath<Vector3>()
|
||||||
|
path.add(new CatmullRomCurve3(points.map((p) => new Vector3(p.x, p.y, 0))))
|
||||||
|
|
||||||
|
return new ExtrudeGeometry(shape, {
|
||||||
|
steps: 100,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const length = arcStart.getLength()
|
||||||
|
const totalDashes = length / (dashSize + gapSize) // rounding makes the dashes jittery since the new dash is suddenly appears instead of growing into place
|
||||||
|
const dashesAtEachEnd = Math.min(100, totalDashes / 2) // Assuming we want 50 dashes total, 25 at each end
|
||||||
|
|
||||||
|
const dashGeometries = []
|
||||||
|
|
||||||
|
// Function to create a dash at a specific t value (0 to 1 along the curve)
|
||||||
|
const createDashAt = (t: number, curve: EllipseCurve) => {
|
||||||
|
const startVec = curve.getPoint(t)
|
||||||
|
const endVec = curve.getPoint(Math.min(0.5, t + dashSize / length))
|
||||||
|
const midVec = curve.getPoint(Math.min(0.5, t + dashSize / length / 2))
|
||||||
|
const dashCurve = new CurvePath<Vector3>()
|
||||||
|
dashCurve.add(
|
||||||
|
new CatmullRomCurve3([
|
||||||
|
new Vector3(startVec.x, startVec.y, 0),
|
||||||
|
new Vector3(midVec.x, midVec.y, 0),
|
||||||
|
new Vector3(endVec.x, endVec.y, 0),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
return new ExtrudeGeometry(shape, {
|
||||||
|
steps: 3,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: dashCurve,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create dashes at the start of the arc
|
||||||
|
for (let i = 0; i < dashesAtEachEnd; i++) {
|
||||||
|
const t = i / totalDashes
|
||||||
|
dashGeometries.push(createDashAt(t, arcStart))
|
||||||
|
dashGeometries.push(createDashAt(t, arcEnd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill in the remaining arc
|
||||||
|
const remainingArcLength = length - dashesAtEachEnd * 2 * (dashSize + gapSize)
|
||||||
|
if (remainingArcLength > 0) {
|
||||||
|
const remainingArcStartT = dashesAtEachEnd / totalDashes
|
||||||
|
const remainingArcEndT = 1 - remainingArcStartT
|
||||||
|
const centerVec = new Vector2(center[0], center[1])
|
||||||
|
const remainingArcStartVec = arcStart.getPoint(remainingArcStartT)
|
||||||
|
const remainingArcEndVec = arcStart.getPoint(remainingArcEndT)
|
||||||
|
const remainingArcCurve = new EllipseCurve(
|
||||||
|
arcStart.aX,
|
||||||
|
arcStart.aY,
|
||||||
|
arcStart.xRadius,
|
||||||
|
arcStart.yRadius,
|
||||||
|
new Vector2().subVectors(centerVec, remainingArcStartVec).angle() +
|
||||||
|
Math.PI,
|
||||||
|
new Vector2().subVectors(centerVec, remainingArcEndVec).angle() + Math.PI,
|
||||||
|
!ccw
|
||||||
|
)
|
||||||
|
const remainingArcPoints = remainingArcCurve.getPoints(50)
|
||||||
|
const remainingArcPath = new CurvePath<Vector3>()
|
||||||
|
remainingArcPath.add(
|
||||||
|
new CatmullRomCurve3(
|
||||||
|
remainingArcPoints.map((p) => new Vector3(p.x, p.y, 0))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const remainingArcGeometry = new ExtrudeGeometry(shape, {
|
||||||
|
steps: 50,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: remainingArcPath,
|
||||||
|
})
|
||||||
|
dashGeometries.push(remainingArcGeometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
const geo = dashGeometries.length
|
||||||
|
? mergeGeometries(dashGeometries)
|
||||||
|
: new BufferGeometry()
|
||||||
|
geo.userData.type = 'dashed'
|
||||||
|
return geo
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dashedStraight(
|
||||||
|
from: Coords2d,
|
||||||
|
to: Coords2d,
|
||||||
|
shape: Shape,
|
||||||
|
scale = 1
|
||||||
|
): BufferGeometry<NormalBufferAttributes> {
|
||||||
|
const dashSize = 1.2 * scale
|
||||||
|
const gapSize = 1.2 * scale // todo: gabSize is not respected
|
||||||
|
const dashLine = new LineCurve3(
|
||||||
|
new Vector3(from[0], from[1], 0),
|
||||||
|
new Vector3(to[0], to[1], 0)
|
||||||
|
)
|
||||||
|
const length = dashLine.getLength()
|
||||||
|
const numberOfPoints = (length / (dashSize + gapSize)) * 2
|
||||||
|
const startOfLine = new Vector3(from[0], from[1], 0)
|
||||||
|
const endOfLine = new Vector3(to[0], to[1], 0)
|
||||||
|
const dashGeometries = []
|
||||||
|
const dashComponent = (xOrY: number, pointIndex: number) =>
|
||||||
|
((to[xOrY] - from[xOrY]) / numberOfPoints) * pointIndex + from[xOrY]
|
||||||
|
for (let i = 0; i < numberOfPoints; i += 2) {
|
||||||
|
const dashStart = new Vector3(dashComponent(0, i), dashComponent(1, i), 0)
|
||||||
|
let dashEnd = new Vector3(
|
||||||
|
dashComponent(0, i + 1),
|
||||||
|
dashComponent(1, i + 1),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
if (startOfLine.distanceTo(dashEnd) > startOfLine.distanceTo(endOfLine))
|
||||||
|
dashEnd = endOfLine
|
||||||
|
|
||||||
|
if (dashEnd) {
|
||||||
|
const dashCurve = new LineCurve3(dashStart, dashEnd)
|
||||||
|
const dashGeometry = new ExtrudeGeometry(shape, {
|
||||||
|
steps: 1,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: dashCurve,
|
||||||
|
})
|
||||||
|
dashGeometries.push(dashGeometry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const geo = dashGeometries.length
|
||||||
|
? mergeGeometries(dashGeometries)
|
||||||
|
: new BufferGeometry()
|
||||||
|
geo.userData.type = 'dashed'
|
||||||
|
return geo
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { paths } from '../Router'
|
import { paths } from 'lib/paths'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import type { LinkProps } from 'react-router-dom'
|
import type { LinkProps } from 'react-router-dom'
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Toolbar } from '../Toolbar'
|
import { Toolbar } from '../Toolbar'
|
||||||
import UserSidebarMenu from './UserSidebarMenu'
|
import UserSidebarMenu from './UserSidebarMenu'
|
||||||
import { IndexLoaderData } from '../Router'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
} from '../lang/modifyAst'
|
} from '../lang/modifyAst'
|
||||||
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
import { kclManager, useKclContext } from 'lang/KclSinglton'
|
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { executeAst } from 'useStore'
|
import { executeAst } from 'useStore'
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ export function useCalc({
|
|||||||
inputRef: React.RefObject<HTMLInputElement>
|
inputRef: React.RefObject<HTMLInputElement>
|
||||||
valueNode: Value | null
|
valueNode: Value | null
|
||||||
calcResult: string
|
calcResult: string
|
||||||
prevVariables: PrevVariable<any>[]
|
prevVariables: PrevVariable<unknown>[]
|
||||||
newVariableName: string
|
newVariableName: string
|
||||||
isNewVariableNameUnique: boolean
|
isNewVariableNameUnique: boolean
|
||||||
newVariableInsertIndex: number
|
newVariableInsertIndex: number
|
||||||
@ -148,7 +148,6 @@ export function useCalc({
|
|||||||
executeAst({
|
executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
defaultPlanes: kclManager.defaultPlanes,
|
|
||||||
useFakeExecutor: true,
|
useFakeExecutor: true,
|
||||||
programMemoryOverride: JSON.parse(
|
programMemoryOverride: JSON.parse(
|
||||||
JSON.stringify(kclManager.programMemory)
|
JSON.stringify(kclManager.programMemory)
|
||||||
|
75
src/components/CamToggle.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { sceneInfra } from '../clientSideScene/sceneInfra'
|
||||||
|
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||||
|
import { throttle, isReducedMotion } from 'lib/utils'
|
||||||
|
|
||||||
|
const updateDollyZoom = throttle(
|
||||||
|
(newFov: number) => sceneInfra.camControls.dollyZoom(newFov),
|
||||||
|
1000 / 15
|
||||||
|
)
|
||||||
|
|
||||||
|
export const CamToggle = () => {
|
||||||
|
const [isPerspective, setIsPerspective] = useState(true)
|
||||||
|
const [fov, setFov] = useState(40)
|
||||||
|
const [enableRotate, setEnableRotate] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
engineCommandManager.waitForReady.then(async () => {
|
||||||
|
sceneInfra.camControls.dollyZoom(fov)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const toggleCamera = () => {
|
||||||
|
if (isPerspective) {
|
||||||
|
isReducedMotion()
|
||||||
|
? sceneInfra.camControls.useOrthographicCamera()
|
||||||
|
: sceneInfra.camControls.animateToOrthographic()
|
||||||
|
} else {
|
||||||
|
isReducedMotion()
|
||||||
|
? sceneInfra.camControls.usePerspectiveCamera()
|
||||||
|
: sceneInfra.camControls.animateToPerspective()
|
||||||
|
}
|
||||||
|
setIsPerspective(!isPerspective)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFovChange = (newFov: number) => {
|
||||||
|
setFov(newFov)
|
||||||
|
updateDollyZoom(newFov)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="absolute right-14 bottom-3">
|
||||||
|
{isPerspective && (
|
||||||
|
<div className="">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="4"
|
||||||
|
max="90"
|
||||||
|
step={0.5}
|
||||||
|
value={fov}
|
||||||
|
onChange={(e) => handleFovChange(Number(e.target.value))}
|
||||||
|
className="w-full cursor-pointer pointer-events-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button onClick={toggleCamera} className="">
|
||||||
|
{isPerspective
|
||||||
|
? 'Switch to Orthographic Camera'
|
||||||
|
: 'Switch to Perspective Camera'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (enableRotate) {
|
||||||
|
sceneInfra.camControls.enableRotate = false
|
||||||
|
} else {
|
||||||
|
sceneInfra.camControls.enableRotate = true
|
||||||
|
}
|
||||||
|
setEnableRotate(!enableRotate)
|
||||||
|
}}
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
{enableRotate ? 'Disable Rotation' : 'Enable Rotation'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -9,7 +9,7 @@ import styles from './CodeMenu.module.css'
|
|||||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||||
import { editorShortcutMeta } from './TextEditor'
|
import { editorShortcutMeta } from './TextEditor'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||||
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
||||||
@ -77,6 +77,24 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
|||||||
</small>
|
</small>
|
||||||
</a>
|
</a>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
<a
|
||||||
|
className={styles.button}
|
||||||
|
href="https://github.com/KittyCAD/kcl-samples"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<span>KCL samples</span>
|
||||||
|
<small>
|
||||||
|
On GitHub
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="ml-1 align-text-top"
|
||||||
|
width={12}
|
||||||
|
/>
|
||||||
|
</small>
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
</Menu.Items>
|
</Menu.Items>
|
||||||
</div>
|
</div>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Combobox } from '@headlessui/react'
|
import { Combobox } from '@headlessui/react'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { CommandArgumentOption } from 'lib/commandTypes'
|
import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
function CommandArgOptionInput({
|
function CommandArgOptionInput({
|
||||||
options,
|
options,
|
||||||
@ -11,51 +11,89 @@ function CommandArgOptionInput({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
placeholder,
|
placeholder,
|
||||||
}: {
|
}: {
|
||||||
options: CommandArgumentOption<unknown>[]
|
options: (CommandArgument<unknown> & { inputType: 'options' })['options']
|
||||||
argName: string
|
argName: string
|
||||||
stepBack: () => void
|
stepBack: () => void
|
||||||
onSubmit: (data: unknown) => void
|
onSubmit: (data: unknown) => void
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
}) {
|
}) {
|
||||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
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 inputRef = useRef<HTMLInputElement>(null)
|
||||||
const formRef = useRef<HTMLFormElement>(null)
|
const formRef = useRef<HTMLFormElement>(null)
|
||||||
const [argValue, setArgValue] = useState<(typeof options)[number]['value']>(
|
const [selectedOption, setSelectedOption] = useState<
|
||||||
options.find((o) => 'isCurrent' in o && o.isCurrent)?.value ||
|
CommandArgumentOption<unknown>
|
||||||
commandBarState.context.argumentsToSubmit[argName] ||
|
>(currentOption || resolvedOptions[0])
|
||||||
options[0].value
|
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 [query, setQuery] = useState('')
|
|
||||||
const [filteredOptions, setFilteredOptions] = useState<typeof options>()
|
|
||||||
|
|
||||||
const fuse = new Fuse(options, {
|
// Reset the query and selected option when the argName changes
|
||||||
keys: ['name', 'description'],
|
useEffect(() => {
|
||||||
threshold: 0.3,
|
setQuery(initialQuery)
|
||||||
})
|
setSelectedOption(currentOption || resolvedOptions[0])
|
||||||
|
}, [argName])
|
||||||
|
|
||||||
|
// Auto focus and select the input when the component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
inputRef.current?.select()
|
inputRef.current?.select()
|
||||||
}, [inputRef])
|
}, [inputRef])
|
||||||
|
|
||||||
|
// Filter the options based on the query,
|
||||||
|
// resetting the query when the options change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const results = fuse.search(query).map((result) => result.item)
|
const results = fuse.search(query).map((result) => result.item)
|
||||||
setFilteredOptions(query.length > 0 ? results : options)
|
setFilteredOptions(query.length > 0 ? results : resolvedOptions)
|
||||||
}, [query])
|
}, [query, resolvedOptions, fuse])
|
||||||
|
|
||||||
function handleSelectOption(option: CommandArgumentOption<unknown>) {
|
function handleSelectOption(option: CommandArgumentOption<unknown>) {
|
||||||
setArgValue(option)
|
// We deal with the whole option object internally
|
||||||
|
setSelectedOption(option)
|
||||||
|
|
||||||
|
// But we only submit the value
|
||||||
onSubmit(option.value)
|
onSubmit(option.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
onSubmit(argValue)
|
|
||||||
|
// We submit the value of the selected option, not the whole object
|
||||||
|
onSubmit(selectedOption.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form id="arg-form" onSubmit={handleSubmit} ref={formRef}>
|
<form id="arg-form" onSubmit={handleSubmit} ref={formRef}>
|
||||||
<Combobox value={argValue} onChange={handleSelectOption} name="options">
|
<Combobox
|
||||||
|
value={selectedOption}
|
||||||
|
onChange={handleSelectOption}
|
||||||
|
name="options"
|
||||||
|
>
|
||||||
<div className="flex items-center mx-4 mt-4 mb-2">
|
<div className="flex items-center mx-4 mt-4 mb-2">
|
||||||
<label
|
<label
|
||||||
htmlFor="option-input"
|
htmlFor="option-input"
|
||||||
@ -75,10 +113,12 @@ function CommandArgOptionInput({
|
|||||||
stepBack()
|
stepBack()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
value={query}
|
||||||
placeholder={
|
placeholder={
|
||||||
(argValue as CommandArgumentOption<unknown>)?.name ||
|
currentOption?.name ||
|
||||||
placeholder ||
|
placeholder ||
|
||||||
'Select an option for ' + argName
|
argName ||
|
||||||
|
'Select an option'
|
||||||
}
|
}
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -98,7 +138,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"
|
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>
|
<p className="flex-grow">{option.name} </p>
|
||||||
{'isCurrent' in option && option.isCurrent && (
|
{option.value === currentOption?.value && (
|
||||||
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
||||||
current
|
current
|
||||||
</small>
|
</small>
|
||||||
|
@ -27,13 +27,8 @@ export const CommandBarProvider = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const [commandBarState, commandBarSend] = useMachine(commandBarMachine, {
|
const [commandBarState, commandBarSend] = useMachine(commandBarMachine, {
|
||||||
|
devTools: true,
|
||||||
guards: {
|
guards: {
|
||||||
'Arguments are ready': (context, _) => {
|
|
||||||
return context.selectedCommand?.args
|
|
||||||
? context.argumentsToSubmit.length ===
|
|
||||||
Object.keys(context.selectedCommand.args)?.length
|
|
||||||
: false
|
|
||||||
},
|
|
||||||
'Command has no arguments': (context, _event) => {
|
'Command has no arguments': (context, _event) => {
|
||||||
return (
|
return (
|
||||||
!context.selectedCommand?.args ||
|
!context.selectedCommand?.args ||
|
||||||
@ -56,12 +51,11 @@ export const CommandBarProvider = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<CommandBar />
|
|
||||||
</CommandsContext.Provider>
|
</CommandsContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommandBar = () => {
|
export const CommandBar = () => {
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||||
const {
|
const {
|
||||||
context: { selectedCommand, currentArgument, commands },
|
context: { selectedCommand, currentArgument, commands },
|
||||||
@ -81,17 +75,23 @@ const CommandBar = () => {
|
|||||||
function stepBack() {
|
function stepBack() {
|
||||||
if (!currentArgument) {
|
if (!currentArgument) {
|
||||||
if (commandBarState.matches('Review')) {
|
if (commandBarState.matches('Review')) {
|
||||||
const entries = Object.entries(selectedCommand?.args || {})
|
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],
|
||||||
|
}
|
||||||
|
|
||||||
commandBarSend({
|
commandBarSend({
|
||||||
type: commandBarState.matches('Review')
|
type: 'Edit argument',
|
||||||
? 'Edit argument'
|
|
||||||
: 'Change current argument',
|
|
||||||
data: {
|
data: {
|
||||||
arg: {
|
arg: currentArg,
|
||||||
name: entries[entries.length - 1][0],
|
|
||||||
...entries[entries.length - 1][1],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -146,6 +146,7 @@ const CommandBar = () => {
|
|||||||
<WrapperComponent.Panel
|
<WrapperComponent.Panel
|
||||||
className="relative z-50 pointer-events-auto w-full max-w-xl py-2 mx-auto border rounded shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70"
|
className="relative z-50 pointer-events-auto w-full max-w-xl py-2 mx-auto border rounded shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70"
|
||||||
as="div"
|
as="div"
|
||||||
|
data-testid="command-bar"
|
||||||
>
|
>
|
||||||
{commandBarState.matches('Selecting command') ? (
|
{commandBarState.matches('Selecting command') ? (
|
||||||
<CommandComboBox options={commands} />
|
<CommandComboBox options={commands} />
|
||||||
|