Compare commits
259 Commits
kurt-repli
...
v0.15.3
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
df8c17ac18 | |||
9d40f282a8 | |||
a61d931826 | |||
418350ddbc | |||
d43abe20d9 | |||
84380f3da9 | |||
eea55ff2b1 | |||
10b6c1cfbc | |||
d5570e5c62 | |||
0c9589f7ee | |||
ddf66c1e0f | |||
cf1f2bd235 | |||
0b5bb5f77d | |||
0825cb5a59 | |||
4ec94a721c | |||
16dd5aab96 | |||
bf68a87897 | |||
c6e97e729a | |||
d2535bb8c2 | |||
b01357b49e | |||
793e3510cc | |||
04ae8141c3 | |||
3ae5393dd7 | |||
38119d5a3b | |||
b453b4b453 | |||
3972431cb4 | |||
884545fcde | |||
6deb242eb5 | |||
77fa9af71e | |||
6a9a0a8bd7 | |||
90e432b10e | |||
90499e086f | |||
8b398a8dd5 | |||
23d2dc8dc8 | |||
764a73ec8b | |||
b69451d2fe | |||
173d50517c | |||
3b63632005 | |||
2bd3b06178 | |||
9c58cde35f | |||
3eb92bb0c4 | |||
f3083eb59d | |||
cef29013b8 | |||
58d1303468 | |||
7c11b7b739 | |||
9f27f3c1ce | |||
f6cbc752d7 | |||
6df1ae7161 | |||
159ec08211 | |||
6aab9c6e23 | |||
afd8daae15 | |||
1132779b4b | |||
e3a65f5b3f | |||
d53665a12a | |||
447f4f9f8f | |||
859927c06d | |||
b88425efc8 | |||
c37dfc61ef | |||
b170ac739f | |||
d712add4da | |||
d8aad4bd4f | |||
1f1c44e598 | |||
b20e685eea | |||
3690d986c1 | |||
9a7f434ede | |||
6afacd7427 | |||
957001ee88 | |||
8b4cc306af | |||
52d88171ca | |||
9142cf3af7 | |||
361500058c | |||
198479a71a | |||
905784c1e5 | |||
c33aaad800 | |||
d175c75780 | |||
ba348d1222 | |||
1f49ddfc29 | |||
58659652c1 | |||
251971238d | |||
381d0b3bc8 | |||
fa7943d06a | |||
7a384251d4 | |||
8e07ea32a6 | |||
23adf9d905 | |||
9f0ac5f6fd | |||
08dbd2e9c3 | |||
2e2ba5adbd | |||
a21dbf1055 | |||
5ecb176467 | |||
66135636ec | |||
685a16545c | |||
9adb15ee93 | |||
a8c4c97d79 | |||
39e8e1f259 | |||
1672c1fd1f | |||
6ec5881985 |
@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
ignore-words-list: crate,everytime
|
||||
skip: **/target,node_modules,build
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo
|
||||
skip: **/target,node_modules,build,**/Cargo.lock
|
||||
|
@ -1,6 +1,6 @@
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
|
||||
VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
||||
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||
VITE_KC_SKIP_AUTH=false
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||
VITE_KC_SENTRY_DSN=
|
||||
|
@ -1,6 +1,6 @@
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.kittycad.io
|
||||
VITE_KC_SITE_BASE_URL=https://kittycad.io
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.zoo.dev
|
||||
VITE_KC_SITE_BASE_URL=https://zoo.dev
|
||||
VITE_KC_SKIP_AUTH=false
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
||||
VITE_KC_SENTRY_DSN=https://a814f2f66734989a90367f48feee28ca@o1042111.ingest.sentry.io/4505789425844224
|
||||
VITE_KC_SENTRY_DSN=
|
||||
|
@ -1 +1,2 @@
|
||||
src/wasm-lib/*
|
||||
*.typegen.ts
|
||||
|
18
.eslintrc
@ -1,4 +1,8 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": [
|
||||
"css-modules"
|
||||
],
|
||||
@ -11,6 +15,16 @@
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
]
|
||||
}
|
||||
],
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
|
||||
"rules": {
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"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
|
||||
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
|
||||
run: |
|
||||
cd "${{ matrix.dir }}"
|
||||
|
10
.github/workflows/cargo-test.yml
vendored
@ -44,16 +44,6 @@ jobs:
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- name: Rust Cache
|
||||
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
|
||||
shell: bash
|
||||
run: |-
|
||||
|
108
.github/workflows/ci.yml
vendored
@ -31,7 +31,6 @@ jobs:
|
||||
- run: yarn install
|
||||
- run: yarn fmt-check
|
||||
|
||||
|
||||
check-types:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@ -47,6 +46,7 @@ jobs:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
- run: yarn xstate:typegen
|
||||
- run: yarn tsc
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
- name: Install codespell
|
||||
run: |
|
||||
python -m pip install codespell
|
||||
@ -86,8 +86,6 @@ jobs:
|
||||
|
||||
- run: yarn test:nowatch
|
||||
|
||||
- run: yarn test:cov
|
||||
|
||||
|
||||
prepare-json-files:
|
||||
runs-on: ubuntu-latest # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||
@ -105,7 +103,7 @@ jobs:
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
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
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
@ -124,8 +122,9 @@ jobs:
|
||||
needs: [prepare-json-files]
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
os: [macos-14, ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -182,13 +181,16 @@ jobs:
|
||||
cd ../../
|
||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
|
||||
- name: Run vite build (build:both)
|
||||
run: yarn vite build --mode ${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||
|
||||
- name: Fix format
|
||||
run: yarn fmt
|
||||
|
||||
- name: Install Universal target (MacOS only)
|
||||
if: matrix.os == 'macos-latest'
|
||||
- name: Install x86 target for Universal builds (MacOS only)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
rustup target add aarch64-apple-darwin
|
||||
rustup target add x86_64-apple-darwin
|
||||
|
||||
- name: Prepare certificate and variables (Windows only)
|
||||
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
|
||||
@ -222,7 +224,7 @@ jobs:
|
||||
with:
|
||||
includeRelease: false
|
||||
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
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
@ -238,27 +240,26 @@ jobs:
|
||||
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' }}"
|
||||
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
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
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' }}
|
||||
with:
|
||||
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
||||
|
||||
- name: Install tauri-driver for e2e tests (linux only)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: tauri-driver
|
||||
|
||||
- name: Run e2e tests (linux only)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: xvfb-run yarn test:e2e
|
||||
run: |
|
||||
cargo install tauri-driver@0.1.3
|
||||
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||
export VITE_KC_API_BASE_URL
|
||||
xvfb-run yarn test:e2e:tauri
|
||||
env:
|
||||
MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}
|
||||
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 }}
|
||||
|
||||
|
||||
publish-apps-release:
|
||||
@ -271,26 +272,24 @@ jobs:
|
||||
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) }}
|
||||
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:
|
||||
- uses: actions/download-artifact@v3
|
||||
|
||||
- name: Generate the update static endpoint
|
||||
run: |
|
||||
ls -l artifact/*/*itty*
|
||||
ls -l artifact/*/*oo*
|
||||
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`
|
||||
RELEASE_DIR=https://${BUCKET_DIR}/${VERSION}
|
||||
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
||||
jq --null-input \
|
||||
--arg version "${VERSION}" \
|
||||
--arg pub_date "${PUB_DATE}" \
|
||||
--arg notes "${NOTES}" \
|
||||
--arg darwin_sig "$DARWIN_SIG" \
|
||||
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
|
||||
--arg linux_sig "$LINUX_SIG" \
|
||||
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage.tar.gz" \
|
||||
--arg darwin_url "$RELEASE_DIR/macos/Zoo%20Modeling%20App.app.tar.gz" \
|
||||
--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,
|
||||
"pub_date": $pub_date,
|
||||
@ -304,10 +303,6 @@ jobs:
|
||||
"signature": $darwin_sig,
|
||||
"url": $darwin_url
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"signature": $linux_sig,
|
||||
"url": $linux_url
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"signature": $windows_sig,
|
||||
"url": $windows_url
|
||||
@ -318,14 +313,13 @@ jobs:
|
||||
|
||||
- name: Generate the download static endpoint
|
||||
run: |
|
||||
RELEASE_DIR=https://${BUCKET_DIR}/${VERSION}
|
||||
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
||||
jq --null-input \
|
||||
--arg version "${VERSION}" \
|
||||
--arg pub_date "${PUB_DATE}" \
|
||||
--arg notes "${NOTES}" \
|
||||
--arg darwin_url "$RELEASE_DIR/dmg/KittyCAD%20Modeling_${VERSION_NO_V}_universal.dmg" \
|
||||
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage" \
|
||||
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi" \
|
||||
--arg darwin_url "$RELEASE_DIR/dmg/Zoo%20Modeling%20App_${VERSION_NO_V}_universal.dmg" \
|
||||
--arg windows_url "$RELEASE_DIR/msi/Zoo%20Modeling%20App_${VERSION_NO_V}_x64_en-US.msi" \
|
||||
'{
|
||||
"version": $version,
|
||||
"pub_date": $pub_date,
|
||||
@ -334,9 +328,6 @@ jobs:
|
||||
"dmg-universal": {
|
||||
"url": $darwin_url
|
||||
},
|
||||
"appimage-x86_64": {
|
||||
"url": $linux_url
|
||||
},
|
||||
"msi-x86_64": {
|
||||
"url": $windows_url
|
||||
}
|
||||
@ -345,31 +336,31 @@ jobs:
|
||||
cat last_download.json
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
uses: 'google-github-actions/auth@v1.1.1'
|
||||
uses: 'google-github-actions/auth@v2.1.1'
|
||||
with:
|
||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||
|
||||
- name: Set up Google Cloud SDK
|
||||
uses: google-github-actions/setup-gcloud@v1.1.1
|
||||
uses: google-github-actions/setup-gcloud@v2.1.0
|
||||
with:
|
||||
project_id: kittycadapi
|
||||
|
||||
- name: Upload release files to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v1.0.3
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||
with:
|
||||
path: artifact
|
||||
glob: '*/*itty*'
|
||||
glob: '*/Zoo*'
|
||||
parent: false
|
||||
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
||||
|
||||
- name: Upload update endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v1.0.3
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||
with:
|
||||
path: last_update.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload download endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v1.0.3
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||
with:
|
||||
path: last_download.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
@ -378,4 +369,29 @@ jobs:
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
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
|
116
.github/workflows/playwright.yml
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
playwright-ubuntu:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- uses: KittyCAD/action-install-cli@v0.2.21
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
- name: Install Playwright Browsers
|
||||
run: yarn playwright install --with-deps
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cache wasm
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: build wasm
|
||||
run: yarn build:wasm
|
||||
- name: build web
|
||||
run: yarn build:local
|
||||
- name: Run ubuntu/chrome snapshots
|
||||
run: yarn playwright test --project="Google Chrome" --update-snapshots e2e/playwright/snapshot-tests.spec.ts
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
- name: check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
git add .
|
||||
if git status | grep -q "Changes to be committed"
|
||||
then
|
||||
echo "::set-output name=modified::true"
|
||||
else
|
||||
echo "::set-output name=modified::false"
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
run: |
|
||||
git add .
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
# TODO when safari works on ubuntu remove the os part of the commit message
|
||||
git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
- name: Run ubuntu/chrome flow
|
||||
run: yarn playwright test --project="Google Chrome" e2e/playwright/flow-tests.spec.ts
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
playwright-macos:
|
||||
timeout-minutes: 60
|
||||
runs-on: macos-14
|
||||
needs: playwright-ubuntu
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
- name: Install Playwright Browsers
|
||||
run: yarn playwright install --with-deps
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cache wasm
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: build wasm
|
||||
run: yarn build:wasm
|
||||
- name: build web
|
||||
run: yarn build:local
|
||||
- name: Run macos/safari flow
|
||||
# safari doesn't work on Ubuntu because of the same reason tauri doesn't (webRTC issues)
|
||||
# TODO remove this and the matrix and run all tests on ubuntu when this is fixed
|
||||
run: yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
23
.gitignore
vendored
@ -33,3 +33,26 @@ src/wasm-lib/bindings
|
||||
src/wasm-lib/kcl/bindings
|
||||
public/wasm_lib_bg.wasm
|
||||
src/wasm-lib/lcov.info
|
||||
|
||||
e2e/playwright/playwright-secrets.env
|
||||
e2e/playwright/temp1.png
|
||||
e2e/playwright/temp2.png
|
||||
# exports from snapshot-tests.spec.ts
|
||||
e2e/playwright/export-snapshots/*.ply
|
||||
e2e/playwright/export-snapshots/*.obj
|
||||
e2e/playwright/export-snapshots/*.step
|
||||
e2e/playwright/export-snapshots/*.stl
|
||||
e2e/playwright/export-snapshots/*binary.gltf
|
||||
e2e/playwright/export-snapshots/*embedded.gltf
|
||||
|
||||
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
|
||||
## generated files
|
||||
src/**/*.typegen.ts
|
||||
|
||||
src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||
|
@ -7,6 +7,7 @@ coverage
|
||||
target
|
||||
src/wasm-lib/pkg
|
||||
src/wasm-lib/kcl/bindings
|
||||
e2e/playwright/export-snapshots
|
||||
|
||||
# XState generated files
|
||||
src/machines/modelingMachine.typegen.ts
|
||||
src/machines/**.typegen.ts
|
||||
|
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 The KittyCAD Authors
|
||||
Copyright (c) 2023 The Zoo Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
131
README.md
@ -1,17 +1,17 @@
|
||||

|
||||

|
||||
|
||||
## KittyCAD Modeling App
|
||||
## Zoo Modeling App
|
||||
|
||||
live at [app.kittycad.io](https://app.kittycad.io/)
|
||||
live at [app.zoo.dev](https://app.zoo.dev/)
|
||||
|
||||
A CAD application from the future, brought to you by the [KittyCAD team](https://kittycad.io).
|
||||
A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
|
||||
|
||||
The KittyCAD modeling app is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence:
|
||||
Modeling App is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence:
|
||||
|
||||
- All artifacts—including parts and assemblies—should be represented as human-readable code. At the end of the day, your CAD project should be "plain text"
|
||||
- This makes version control—which is a solved problem in software engineering—trivial for CAD
|
||||
- All GUI (or point-and-click) interactions should be actions performed on this code representation under the hood
|
||||
- This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in KittyCAD Modeling App
|
||||
- This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in Modeling App
|
||||
- Everything graphics _has_ to be built for the GPU
|
||||
- Most CAD applications have had to retrofit support for GPUs, but our geometry engine is made for GPUs (primarily Nvidia's Vulkan), getting the order of magnitude rendering performance boost with it
|
||||
- Make the resource-intensive pieces of an application auto-scaling
|
||||
@ -19,9 +19,9 @@ The KittyCAD modeling app is our take on what a modern modelling experience can
|
||||
|
||||
We are excited about what a small team of people could build in a short time with our API. We welcome you to try our API, build your own applications, or contribute to ours!
|
||||
|
||||
KittyCAD Modeling App is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more.
|
||||
Modeling App is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more.
|
||||
|
||||
The 3D view in KittyCAD Modeling App is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine.
|
||||
The 3D view in Modeling App is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine.
|
||||
|
||||
## Tools
|
||||
|
||||
@ -94,7 +94,6 @@ For running the rust (not tauri rust though) only, you can
|
||||
cd src/wasm-lib
|
||||
cargo test
|
||||
```
|
||||
but you will need to have install ffmpeg prior to.
|
||||
|
||||
## Tauri
|
||||
|
||||
@ -137,6 +136,11 @@ Before you submit a contribution PR to this repo, please ensure that:
|
||||
VERSION=x.y.z yarn run bump-jsons
|
||||
```
|
||||
|
||||
Alternatively you can try the experimental `make-release.sh` bash script that will create the branch with the updated json files for you.
|
||||
run `./make-release.sh` for a patch update
|
||||
run `./make-release.sh "minor"` for minor
|
||||
run `./make-release.sh "major"` for major
|
||||
|
||||
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and past in the following
|
||||
|
||||
```typescript
|
||||
@ -176,3 +180,112 @@ $ cargo +nightly fuzz run parser
|
||||
|
||||
For more information on fuzzing you can check out
|
||||
[this guide](https://rust-fuzz.github.io/book/cargo-fuzz.html).
|
||||
|
||||
|
||||
### Playwright
|
||||
|
||||
First time running plawright locally, you'll need to add the secrets file
|
||||
```bash
|
||||
touch ./e2e/playwright/playwright-secrets.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:
|
||||
run playwright
|
||||
```
|
||||
yarn playwright test
|
||||
```
|
||||
|
||||
run a specific test suite
|
||||
```
|
||||
yarn playwright test src/e2e-tests/example.spec.ts
|
||||
```
|
||||
|
||||
run a specific test change the test from `test('...` to `test.only('...`
|
||||
(note if you commit this, the tests will instantly fail without running any of the tests)
|
||||
|
||||
run headed
|
||||
```
|
||||
yarn playwright test --headed
|
||||
```
|
||||
|
||||
run with step through debugger
|
||||
```
|
||||
PWDEBUG=1 yarn playwright test
|
||||
```
|
||||
However, if you want a debugger I recommend using VSCode and the `playwright` extension, as the above command is a cruder debugger that steps into every function call which is annoying.
|
||||
With the extension you can set a breakpoint after `waitForDefaultPlanesVisibilityChange` in order to skip app loading, then the vscode debugger's "step over" is much better for being able to stay at the right level of abstraction as you debug the code.
|
||||
|
||||
If you want to limit to a single browser use `--project="webkit"` or `firefox`, `Google Chrome`
|
||||
Or comment out browsers in `playwright.config.ts`.
|
||||
|
||||
note chromium has encoder compat issues which is why were testing against the branded 'Google Chrome'
|
||||
|
||||
You may consider using the VSCode extension, it's useful for running individual threads, but some some reason the "record a test" is locked to chromium with we can't use. A work around is to us the CI `yarn playwright codegen -b wk --load-storage ./store localhost:3000`
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
|
||||
Where `./store` should look like this
|
||||
|
||||
</summary>
|
||||
|
||||
```JSON
|
||||
{
|
||||
"cookies": [],
|
||||
"origins": [
|
||||
{
|
||||
"origin": "http://localhost:3000",
|
||||
"localStorage": [
|
||||
{
|
||||
"name": "store",
|
||||
"value": "{\"state\":{\"openPanes\":[\"code\"]},\"version\":0}"
|
||||
},
|
||||
{
|
||||
"name": "persistCode",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "TOKEN_PERSIST_KEY",
|
||||
"value": "your-token"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
However because much of our tests involve clicking in the stream at specific locations, it's code-gen looks `await page.locator('video').click();` when really we need to use a pixel coord, so I think it's of limited use.
|
||||
|
||||
#### Some notes on CI
|
||||
|
||||
The tests are broken into snapshot tests and non-snapshot tests, and they run in that order, they automatically commit new snap shots, so if you see an image commit check it was an intended change. If we have non-determinism in the snapshots such that they are always committing new images, hopefully this annoyance makes us fix them asap, if you notice this happening let Kurt know. But for the odd occasion `git reset --hard HEAD~ && git push -f` is your friend.
|
||||
|
||||
How to interpret failing playwright tests?
|
||||
If your tests fail, click through to the action and see that the tests failed on a line that includes `await page.getByTestId('loading').waitFor({ state: 'detached' })`, this means the test fail because the stream never started. It's you choice if you want to re-run the test, or ignore the failure.
|
||||
|
||||
We run on ubuntu and macos, because safari doesn't work on linux because of the dreaded "no RTCPeerConnection variable" error. But linux runs first and then macos for the same reason that we limit the number of parallel tests to 1 because we limit stream connections per user, so tests would start failing we if let them run together.
|
||||
|
||||
If something fails on CI you can download the artifact, unzip it and then open `playwright-report/data/<UUID>.zip` with https://trace.playwright.dev/ to see what happened.
|
||||
|
||||
#### Getting started writing a playwright test in our app
|
||||
|
||||
Besides following the instructions above and using the playwright docs, our app is weird because of the whole stream thing, which means our testing is weird. Because we've just figured out this stuff and therefore docs might go stale quick here's a 15min vid/tutorial
|
||||
|
||||
https://github.com/KittyCAD/modeling-app/assets/29681384/6f5e8e85-1003-4fd9-be7f-f36ce833942d
|
||||
|
||||
<details>
|
||||
|
||||
<summary>
|
||||
Ps for the debug panel, the following JSON is useful for snapping the camera
|
||||
</summary>
|
||||
|
||||
```JSON
|
||||
{"type":"modeling_cmd_req","cmd_id":"054e5472-e5e9-4071-92d7-1ce3bac61956","cmd":{"type":"default_camera_look_at","center":{"x":15,"y":0,"z":0},"up":{"x":0,"y":0,"z":1},"vantage":{"x":30,"y":30,"z":30}}}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
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.
|
35935
docs/kcl/std.json
6445
docs/kcl/std.md
BIN
e2e/playwright/export-snapshots/gltf-binary.png
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
e2e/playwright/export-snapshots/gltf-embedded.png
Normal file
After Width: | Height: | Size: 193 KiB |
3056
e2e/playwright/export-snapshots/gltf-standard-2.gltf
Normal file
BIN
e2e/playwright/export-snapshots/gltf-standard.gltf
Normal file
BIN
e2e/playwright/export-snapshots/obj-.png
Normal file
After Width: | Height: | Size: 259 KiB |
BIN
e2e/playwright/export-snapshots/ply-ascii.png
Normal file
After Width: | Height: | Size: 220 KiB |
BIN
e2e/playwright/export-snapshots/ply-binary_big_endian.png
Normal file
After Width: | Height: | Size: 220 KiB |
BIN
e2e/playwright/export-snapshots/ply-binary_little_endian.png
Normal file
After Width: | Height: | Size: 220 KiB |
BIN
e2e/playwright/export-snapshots/step-.png
Normal file
After Width: | Height: | Size: 193 KiB |
494
e2e/playwright/export-snapshots/step-.step
Normal file
@ -0,0 +1,494 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||
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'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1 = (
|
||||
LENGTH_UNIT()
|
||||
NAMED_UNIT(*)
|
||||
SI_UNIT($, .METRE.)
|
||||
);
|
||||
#2 = UNCERTAINTY_MEASURE_WITH_UNIT(0.00001, #1, 'DISTANCE_ACCURACY_VALUE', $);
|
||||
#3 = (
|
||||
GEOMETRIC_REPRESENTATION_CONTEXT(3)
|
||||
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#2))
|
||||
GLOBAL_UNIT_ASSIGNED_CONTEXT((#1))
|
||||
REPRESENTATION_CONTEXT('', '3D')
|
||||
);
|
||||
#4 = CARTESIAN_POINT('NONE', (0, 0, -0));
|
||||
#5 = VERTEX_POINT('NONE', #4);
|
||||
#6 = CARTESIAN_POINT('NONE', (0, -0.64516, -0));
|
||||
#7 = VERTEX_POINT('NONE', #6);
|
||||
#8 = CARTESIAN_POINT('NONE', (0, -0.64516, 2.58064));
|
||||
#9 = VERTEX_POINT('NONE', #8);
|
||||
#10 = CARTESIAN_POINT('NONE', (0, 0, 2.58064));
|
||||
#11 = VERTEX_POINT('NONE', #10);
|
||||
#12 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0));
|
||||
#13 = VERTEX_POINT('NONE', #12);
|
||||
#14 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, 2.58064));
|
||||
#15 = VERTEX_POINT('NONE', #14);
|
||||
#16 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0));
|
||||
#17 = VERTEX_POINT('NONE', #16);
|
||||
#18 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, 2.58064));
|
||||
#19 = VERTEX_POINT('NONE', #18);
|
||||
#20 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0));
|
||||
#21 = VERTEX_POINT('NONE', #20);
|
||||
#22 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, 2.58064));
|
||||
#23 = VERTEX_POINT('NONE', #22);
|
||||
#24 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0));
|
||||
#25 = VERTEX_POINT('NONE', #24);
|
||||
#26 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, 2.58064));
|
||||
#27 = VERTEX_POINT('NONE', #26);
|
||||
#28 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0));
|
||||
#29 = VERTEX_POINT('NONE', #28);
|
||||
#30 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, 2.58064));
|
||||
#31 = VERTEX_POINT('NONE', #30);
|
||||
#32 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0));
|
||||
#33 = VERTEX_POINT('NONE', #32);
|
||||
#34 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, 2.58064));
|
||||
#35 = VERTEX_POINT('NONE', #34);
|
||||
#36 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0));
|
||||
#37 = VERTEX_POINT('NONE', #36);
|
||||
#38 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, 2.58064));
|
||||
#39 = VERTEX_POINT('NONE', #38);
|
||||
#40 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0));
|
||||
#41 = VERTEX_POINT('NONE', #40);
|
||||
#42 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, 2.58064));
|
||||
#43 = VERTEX_POINT('NONE', #42);
|
||||
#44 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0));
|
||||
#45 = VERTEX_POINT('NONE', #44);
|
||||
#46 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, 2.58064));
|
||||
#47 = VERTEX_POINT('NONE', #46);
|
||||
#48 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0));
|
||||
#49 = VERTEX_POINT('NONE', #48);
|
||||
#50 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, 2.58064));
|
||||
#51 = VERTEX_POINT('NONE', #50);
|
||||
#52 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0));
|
||||
#53 = VERTEX_POINT('NONE', #52);
|
||||
#54 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, 2.58064));
|
||||
#55 = VERTEX_POINT('NONE', #54);
|
||||
#56 = CARTESIAN_POINT('NONE', (0, 0.64516, -0));
|
||||
#57 = VERTEX_POINT('NONE', #56);
|
||||
#58 = CARTESIAN_POINT('NONE', (0, 0.64516, 2.58064));
|
||||
#59 = VERTEX_POINT('NONE', #58);
|
||||
#60 = DIRECTION('NONE', (0, -1, 0));
|
||||
#61 = VECTOR('NONE', #60, 1);
|
||||
#62 = CARTESIAN_POINT('NONE', (0, 0, -0));
|
||||
#63 = LINE('NONE', #62, #61);
|
||||
#64 = DIRECTION('NONE', (0, 0, 1));
|
||||
#65 = VECTOR('NONE', #64, 1);
|
||||
#66 = CARTESIAN_POINT('NONE', (0, -0.64516, -0));
|
||||
#67 = LINE('NONE', #66, #65);
|
||||
#68 = DIRECTION('NONE', (0, -1, 0));
|
||||
#69 = VECTOR('NONE', #68, 1);
|
||||
#70 = CARTESIAN_POINT('NONE', (0, 0, 2.58064));
|
||||
#71 = LINE('NONE', #70, #69);
|
||||
#72 = DIRECTION('NONE', (0, 0, 1));
|
||||
#73 = VECTOR('NONE', #72, 1);
|
||||
#74 = CARTESIAN_POINT('NONE', (0, 0, -0));
|
||||
#75 = LINE('NONE', #74, #73);
|
||||
#76 = DIRECTION('NONE', (1, 0, 0));
|
||||
#77 = VECTOR('NONE', #76, 1);
|
||||
#78 = CARTESIAN_POINT('NONE', (0, -0.64516, -0));
|
||||
#79 = LINE('NONE', #78, #77);
|
||||
#80 = DIRECTION('NONE', (0, 0, 1));
|
||||
#81 = VECTOR('NONE', #80, 1);
|
||||
#82 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0));
|
||||
#83 = LINE('NONE', #82, #81);
|
||||
#84 = DIRECTION('NONE', (1, 0, 0));
|
||||
#85 = VECTOR('NONE', #84, 1);
|
||||
#86 = CARTESIAN_POINT('NONE', (0, -0.64516, 2.58064));
|
||||
#87 = LINE('NONE', #86, #85);
|
||||
#88 = DIRECTION('NONE', (0.819152044288992, -0.5735764363510459, 0));
|
||||
#89 = VECTOR('NONE', #88, 1);
|
||||
#90 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0));
|
||||
#91 = LINE('NONE', #90, #89);
|
||||
#92 = DIRECTION('NONE', (0, 0, 1));
|
||||
#93 = VECTOR('NONE', #92, 1);
|
||||
#94 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0));
|
||||
#95 = LINE('NONE', #94, #93);
|
||||
#96 = DIRECTION('NONE', (0.819152044288992, -0.5735764363510459, 0));
|
||||
#97 = VECTOR('NONE', #96, 1);
|
||||
#98 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, 2.58064));
|
||||
#99 = LINE('NONE', #98, #97);
|
||||
#100 = DIRECTION('NONE', (1, -0.00000000000000038794063361359933, 0));
|
||||
#101 = VECTOR('NONE', #100, 1);
|
||||
#102 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0));
|
||||
#103 = LINE('NONE', #102, #101);
|
||||
#104 = DIRECTION('NONE', (0, 0, 1));
|
||||
#105 = VECTOR('NONE', #104, 1);
|
||||
#106 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0));
|
||||
#107 = LINE('NONE', #106, #105);
|
||||
#108 = DIRECTION('NONE', (1, -0.00000000000000038794063361359933, 0));
|
||||
#109 = VECTOR('NONE', #108, 1);
|
||||
#110 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, 2.58064));
|
||||
#111 = LINE('NONE', #110, #109);
|
||||
#112 = DIRECTION('NONE', (0, 1, 0));
|
||||
#113 = VECTOR('NONE', #112, 1);
|
||||
#114 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0));
|
||||
#115 = LINE('NONE', #114, #113);
|
||||
#116 = DIRECTION('NONE', (0, 0, 1));
|
||||
#117 = VECTOR('NONE', #116, 1);
|
||||
#118 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0));
|
||||
#119 = LINE('NONE', #118, #117);
|
||||
#120 = DIRECTION('NONE', (0, 1, 0));
|
||||
#121 = VECTOR('NONE', #120, 1);
|
||||
#122 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, 2.58064));
|
||||
#123 = LINE('NONE', #122, #121);
|
||||
#124 = DIRECTION('NONE', (-1, 0, 0));
|
||||
#125 = VECTOR('NONE', #124, 1);
|
||||
#126 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0));
|
||||
#127 = LINE('NONE', #126, #125);
|
||||
#128 = DIRECTION('NONE', (0, 0, 1));
|
||||
#129 = VECTOR('NONE', #128, 1);
|
||||
#130 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0));
|
||||
#131 = LINE('NONE', #130, #129);
|
||||
#132 = DIRECTION('NONE', (-1, 0, 0));
|
||||
#133 = VECTOR('NONE', #132, 1);
|
||||
#134 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, 2.58064));
|
||||
#135 = LINE('NONE', #134, #133);
|
||||
#136 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
|
||||
#137 = VECTOR('NONE', #136, 1);
|
||||
#138 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0));
|
||||
#139 = LINE('NONE', #138, #137);
|
||||
#140 = DIRECTION('NONE', (0, 0, 1));
|
||||
#141 = VECTOR('NONE', #140, 1);
|
||||
#142 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0));
|
||||
#143 = LINE('NONE', #142, #141);
|
||||
#144 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
|
||||
#145 = VECTOR('NONE', #144, 1);
|
||||
#146 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, 2.58064));
|
||||
#147 = LINE('NONE', #146, #145);
|
||||
#148 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406992, 0));
|
||||
#149 = VECTOR('NONE', #148, 1);
|
||||
#150 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0));
|
||||
#151 = LINE('NONE', #150, #149);
|
||||
#152 = DIRECTION('NONE', (0, 0, 1));
|
||||
#153 = VECTOR('NONE', #152, 1);
|
||||
#154 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0));
|
||||
#155 = LINE('NONE', #154, #153);
|
||||
#156 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406992, 0));
|
||||
#157 = VECTOR('NONE', #156, 1);
|
||||
#158 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, 2.58064));
|
||||
#159 = LINE('NONE', #158, #157);
|
||||
#160 = DIRECTION('NONE', (1, -0.0000000000000001378647737807002, 0));
|
||||
#161 = VECTOR('NONE', #160, 1);
|
||||
#162 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0));
|
||||
#163 = LINE('NONE', #162, #161);
|
||||
#164 = DIRECTION('NONE', (0, 0, 1));
|
||||
#165 = VECTOR('NONE', #164, 1);
|
||||
#166 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0));
|
||||
#167 = LINE('NONE', #166, #165);
|
||||
#168 = DIRECTION('NONE', (1, -0.0000000000000001378647737807002, 0));
|
||||
#169 = VECTOR('NONE', #168, 1);
|
||||
#170 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, 2.58064));
|
||||
#171 = LINE('NONE', #170, #169);
|
||||
#172 = DIRECTION('NONE', (0, 1, 0));
|
||||
#173 = VECTOR('NONE', #172, 1);
|
||||
#174 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0));
|
||||
#175 = LINE('NONE', #174, #173);
|
||||
#176 = DIRECTION('NONE', (0, 0, 1));
|
||||
#177 = VECTOR('NONE', #176, 1);
|
||||
#178 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0));
|
||||
#179 = LINE('NONE', #178, #177);
|
||||
#180 = DIRECTION('NONE', (0, 1, 0));
|
||||
#181 = VECTOR('NONE', #180, 1);
|
||||
#182 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, 2.58064));
|
||||
#183 = LINE('NONE', #182, #181);
|
||||
#184 = DIRECTION('NONE', (-1, 0, 0));
|
||||
#185 = VECTOR('NONE', #184, 1);
|
||||
#186 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0));
|
||||
#187 = LINE('NONE', #186, #185);
|
||||
#188 = DIRECTION('NONE', (0, 0, 1));
|
||||
#189 = VECTOR('NONE', #188, 1);
|
||||
#190 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0));
|
||||
#191 = LINE('NONE', #190, #189);
|
||||
#192 = DIRECTION('NONE', (-1, 0, 0));
|
||||
#193 = VECTOR('NONE', #192, 1);
|
||||
#194 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, 2.58064));
|
||||
#195 = LINE('NONE', #194, #193);
|
||||
#196 = DIRECTION('NONE', (-0.90630778703665, -0.4226182617406995, 0));
|
||||
#197 = VECTOR('NONE', #196, 1);
|
||||
#198 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0));
|
||||
#199 = LINE('NONE', #198, #197);
|
||||
#200 = DIRECTION('NONE', (0, 0, 1));
|
||||
#201 = VECTOR('NONE', #200, 1);
|
||||
#202 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0));
|
||||
#203 = LINE('NONE', #202, #201);
|
||||
#204 = DIRECTION('NONE', (-0.90630778703665, -0.4226182617406995, 0));
|
||||
#205 = VECTOR('NONE', #204, 1);
|
||||
#206 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, 2.58064));
|
||||
#207 = LINE('NONE', #206, #205);
|
||||
#208 = DIRECTION('NONE', (-1, 0, 0));
|
||||
#209 = VECTOR('NONE', #208, 1);
|
||||
#210 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0));
|
||||
#211 = LINE('NONE', #210, #209);
|
||||
#212 = DIRECTION('NONE', (0, 0, 1));
|
||||
#213 = VECTOR('NONE', #212, 1);
|
||||
#214 = CARTESIAN_POINT('NONE', (0, 0.64516, -0));
|
||||
#215 = LINE('NONE', #214, #213);
|
||||
#216 = DIRECTION('NONE', (-1, 0, 0));
|
||||
#217 = VECTOR('NONE', #216, 1);
|
||||
#218 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, 2.58064));
|
||||
#219 = LINE('NONE', #218, #217);
|
||||
#220 = DIRECTION('NONE', (0, -1, 0));
|
||||
#221 = VECTOR('NONE', #220, 1);
|
||||
#222 = CARTESIAN_POINT('NONE', (0, 0.64516, -0));
|
||||
#223 = LINE('NONE', #222, #221);
|
||||
#224 = DIRECTION('NONE', (0, -1, 0));
|
||||
#225 = VECTOR('NONE', #224, 1);
|
||||
#226 = CARTESIAN_POINT('NONE', (0, 0.64516, 2.58064));
|
||||
#227 = LINE('NONE', #226, #225);
|
||||
#228 = EDGE_CURVE('NONE', #5, #7, #63, .T.);
|
||||
#229 = EDGE_CURVE('NONE', #7, #9, #67, .T.);
|
||||
#230 = EDGE_CURVE('NONE', #11, #9, #71, .T.);
|
||||
#231 = EDGE_CURVE('NONE', #5, #11, #75, .T.);
|
||||
#232 = EDGE_CURVE('NONE', #7, #13, #79, .T.);
|
||||
#233 = EDGE_CURVE('NONE', #13, #15, #83, .T.);
|
||||
#234 = EDGE_CURVE('NONE', #9, #15, #87, .T.);
|
||||
#235 = EDGE_CURVE('NONE', #13, #17, #91, .T.);
|
||||
#236 = EDGE_CURVE('NONE', #17, #19, #95, .T.);
|
||||
#237 = EDGE_CURVE('NONE', #15, #19, #99, .T.);
|
||||
#238 = EDGE_CURVE('NONE', #17, #21, #103, .T.);
|
||||
#239 = EDGE_CURVE('NONE', #21, #23, #107, .T.);
|
||||
#240 = EDGE_CURVE('NONE', #19, #23, #111, .T.);
|
||||
#241 = EDGE_CURVE('NONE', #21, #25, #115, .T.);
|
||||
#242 = EDGE_CURVE('NONE', #25, #27, #119, .T.);
|
||||
#243 = EDGE_CURVE('NONE', #23, #27, #123, .T.);
|
||||
#244 = EDGE_CURVE('NONE', #25, #29, #127, .T.);
|
||||
#245 = EDGE_CURVE('NONE', #29, #31, #131, .T.);
|
||||
#246 = EDGE_CURVE('NONE', #27, #31, #135, .T.);
|
||||
#247 = EDGE_CURVE('NONE', #29, #33, #139, .T.);
|
||||
#248 = EDGE_CURVE('NONE', #33, #35, #143, .T.);
|
||||
#249 = EDGE_CURVE('NONE', #31, #35, #147, .T.);
|
||||
#250 = EDGE_CURVE('NONE', #33, #37, #151, .T.);
|
||||
#251 = EDGE_CURVE('NONE', #37, #39, #155, .T.);
|
||||
#252 = EDGE_CURVE('NONE', #35, #39, #159, .T.);
|
||||
#253 = EDGE_CURVE('NONE', #37, #41, #163, .T.);
|
||||
#254 = EDGE_CURVE('NONE', #41, #43, #167, .T.);
|
||||
#255 = EDGE_CURVE('NONE', #39, #43, #171, .T.);
|
||||
#256 = EDGE_CURVE('NONE', #41, #45, #175, .T.);
|
||||
#257 = EDGE_CURVE('NONE', #45, #47, #179, .T.);
|
||||
#258 = EDGE_CURVE('NONE', #43, #47, #183, .T.);
|
||||
#259 = EDGE_CURVE('NONE', #45, #49, #187, .T.);
|
||||
#260 = EDGE_CURVE('NONE', #49, #51, #191, .T.);
|
||||
#261 = EDGE_CURVE('NONE', #47, #51, #195, .T.);
|
||||
#262 = EDGE_CURVE('NONE', #49, #53, #199, .T.);
|
||||
#263 = EDGE_CURVE('NONE', #53, #55, #203, .T.);
|
||||
#264 = EDGE_CURVE('NONE', #51, #55, #207, .T.);
|
||||
#265 = EDGE_CURVE('NONE', #53, #57, #211, .T.);
|
||||
#266 = EDGE_CURVE('NONE', #57, #59, #215, .T.);
|
||||
#267 = EDGE_CURVE('NONE', #55, #59, #219, .T.);
|
||||
#268 = EDGE_CURVE('NONE', #57, #5, #223, .T.);
|
||||
#269 = EDGE_CURVE('NONE', #59, #11, #227, .T.);
|
||||
#270 = ORIENTED_EDGE('NONE', *, *, #228, .T.);
|
||||
#271 = ORIENTED_EDGE('NONE', *, *, #229, .T.);
|
||||
#272 = ORIENTED_EDGE('NONE', *, *, #230, .F.);
|
||||
#273 = ORIENTED_EDGE('NONE', *, *, #231, .F.);
|
||||
#274 = EDGE_LOOP('NONE', (#270, #271, #272, #273));
|
||||
#275 = ORIENTED_EDGE('NONE', *, *, #232, .T.);
|
||||
#276 = ORIENTED_EDGE('NONE', *, *, #233, .T.);
|
||||
#277 = ORIENTED_EDGE('NONE', *, *, #234, .F.);
|
||||
#278 = ORIENTED_EDGE('NONE', *, *, #229, .F.);
|
||||
#279 = EDGE_LOOP('NONE', (#275, #276, #277, #278));
|
||||
#280 = ORIENTED_EDGE('NONE', *, *, #235, .T.);
|
||||
#281 = ORIENTED_EDGE('NONE', *, *, #236, .T.);
|
||||
#282 = ORIENTED_EDGE('NONE', *, *, #237, .F.);
|
||||
#283 = ORIENTED_EDGE('NONE', *, *, #233, .F.);
|
||||
#284 = EDGE_LOOP('NONE', (#280, #281, #282, #283));
|
||||
#285 = ORIENTED_EDGE('NONE', *, *, #238, .T.);
|
||||
#286 = ORIENTED_EDGE('NONE', *, *, #239, .T.);
|
||||
#287 = ORIENTED_EDGE('NONE', *, *, #240, .F.);
|
||||
#288 = ORIENTED_EDGE('NONE', *, *, #236, .F.);
|
||||
#289 = EDGE_LOOP('NONE', (#285, #286, #287, #288));
|
||||
#290 = ORIENTED_EDGE('NONE', *, *, #241, .T.);
|
||||
#291 = ORIENTED_EDGE('NONE', *, *, #242, .T.);
|
||||
#292 = ORIENTED_EDGE('NONE', *, *, #243, .F.);
|
||||
#293 = ORIENTED_EDGE('NONE', *, *, #239, .F.);
|
||||
#294 = EDGE_LOOP('NONE', (#290, #291, #292, #293));
|
||||
#295 = ORIENTED_EDGE('NONE', *, *, #244, .T.);
|
||||
#296 = ORIENTED_EDGE('NONE', *, *, #245, .T.);
|
||||
#297 = ORIENTED_EDGE('NONE', *, *, #246, .F.);
|
||||
#298 = ORIENTED_EDGE('NONE', *, *, #242, .F.);
|
||||
#299 = EDGE_LOOP('NONE', (#295, #296, #297, #298));
|
||||
#300 = ORIENTED_EDGE('NONE', *, *, #247, .T.);
|
||||
#301 = ORIENTED_EDGE('NONE', *, *, #248, .T.);
|
||||
#302 = ORIENTED_EDGE('NONE', *, *, #249, .F.);
|
||||
#303 = ORIENTED_EDGE('NONE', *, *, #245, .F.);
|
||||
#304 = EDGE_LOOP('NONE', (#300, #301, #302, #303));
|
||||
#305 = ORIENTED_EDGE('NONE', *, *, #250, .T.);
|
||||
#306 = ORIENTED_EDGE('NONE', *, *, #251, .T.);
|
||||
#307 = ORIENTED_EDGE('NONE', *, *, #252, .F.);
|
||||
#308 = ORIENTED_EDGE('NONE', *, *, #248, .F.);
|
||||
#309 = EDGE_LOOP('NONE', (#305, #306, #307, #308));
|
||||
#310 = ORIENTED_EDGE('NONE', *, *, #253, .T.);
|
||||
#311 = ORIENTED_EDGE('NONE', *, *, #254, .T.);
|
||||
#312 = ORIENTED_EDGE('NONE', *, *, #255, .F.);
|
||||
#313 = ORIENTED_EDGE('NONE', *, *, #251, .F.);
|
||||
#314 = EDGE_LOOP('NONE', (#310, #311, #312, #313));
|
||||
#315 = ORIENTED_EDGE('NONE', *, *, #256, .T.);
|
||||
#316 = ORIENTED_EDGE('NONE', *, *, #257, .T.);
|
||||
#317 = ORIENTED_EDGE('NONE', *, *, #258, .F.);
|
||||
#318 = ORIENTED_EDGE('NONE', *, *, #254, .F.);
|
||||
#319 = EDGE_LOOP('NONE', (#315, #316, #317, #318));
|
||||
#320 = ORIENTED_EDGE('NONE', *, *, #259, .T.);
|
||||
#321 = ORIENTED_EDGE('NONE', *, *, #260, .T.);
|
||||
#322 = ORIENTED_EDGE('NONE', *, *, #261, .F.);
|
||||
#323 = ORIENTED_EDGE('NONE', *, *, #257, .F.);
|
||||
#324 = EDGE_LOOP('NONE', (#320, #321, #322, #323));
|
||||
#325 = ORIENTED_EDGE('NONE', *, *, #262, .T.);
|
||||
#326 = ORIENTED_EDGE('NONE', *, *, #263, .T.);
|
||||
#327 = ORIENTED_EDGE('NONE', *, *, #264, .F.);
|
||||
#328 = ORIENTED_EDGE('NONE', *, *, #260, .F.);
|
||||
#329 = EDGE_LOOP('NONE', (#325, #326, #327, #328));
|
||||
#330 = ORIENTED_EDGE('NONE', *, *, #265, .T.);
|
||||
#331 = ORIENTED_EDGE('NONE', *, *, #266, .T.);
|
||||
#332 = ORIENTED_EDGE('NONE', *, *, #267, .F.);
|
||||
#333 = ORIENTED_EDGE('NONE', *, *, #263, .F.);
|
||||
#334 = EDGE_LOOP('NONE', (#330, #331, #332, #333));
|
||||
#335 = ORIENTED_EDGE('NONE', *, *, #268, .T.);
|
||||
#336 = ORIENTED_EDGE('NONE', *, *, #231, .T.);
|
||||
#337 = ORIENTED_EDGE('NONE', *, *, #269, .F.);
|
||||
#338 = ORIENTED_EDGE('NONE', *, *, #266, .F.);
|
||||
#339 = EDGE_LOOP('NONE', (#335, #336, #337, #338));
|
||||
#340 = ORIENTED_EDGE('NONE', *, *, #228, .T.);
|
||||
#341 = ORIENTED_EDGE('NONE', *, *, #232, .T.);
|
||||
#342 = ORIENTED_EDGE('NONE', *, *, #235, .T.);
|
||||
#343 = ORIENTED_EDGE('NONE', *, *, #238, .T.);
|
||||
#344 = ORIENTED_EDGE('NONE', *, *, #241, .T.);
|
||||
#345 = ORIENTED_EDGE('NONE', *, *, #244, .T.);
|
||||
#346 = ORIENTED_EDGE('NONE', *, *, #247, .T.);
|
||||
#347 = ORIENTED_EDGE('NONE', *, *, #250, .T.);
|
||||
#348 = ORIENTED_EDGE('NONE', *, *, #253, .T.);
|
||||
#349 = ORIENTED_EDGE('NONE', *, *, #256, .T.);
|
||||
#350 = ORIENTED_EDGE('NONE', *, *, #259, .T.);
|
||||
#351 = ORIENTED_EDGE('NONE', *, *, #262, .T.);
|
||||
#352 = ORIENTED_EDGE('NONE', *, *, #265, .T.);
|
||||
#353 = ORIENTED_EDGE('NONE', *, *, #268, .T.);
|
||||
#354 = EDGE_LOOP('NONE', (#340, #341, #342, #343, #344, #345, #346, #347, #348, #349, #350, #351, #352, #353));
|
||||
#355 = ORIENTED_EDGE('NONE', *, *, #230, .T.);
|
||||
#356 = ORIENTED_EDGE('NONE', *, *, #234, .T.);
|
||||
#357 = ORIENTED_EDGE('NONE', *, *, #237, .T.);
|
||||
#358 = ORIENTED_EDGE('NONE', *, *, #240, .T.);
|
||||
#359 = ORIENTED_EDGE('NONE', *, *, #243, .T.);
|
||||
#360 = ORIENTED_EDGE('NONE', *, *, #246, .T.);
|
||||
#361 = ORIENTED_EDGE('NONE', *, *, #249, .T.);
|
||||
#362 = ORIENTED_EDGE('NONE', *, *, #252, .T.);
|
||||
#363 = ORIENTED_EDGE('NONE', *, *, #255, .T.);
|
||||
#364 = ORIENTED_EDGE('NONE', *, *, #258, .T.);
|
||||
#365 = ORIENTED_EDGE('NONE', *, *, #261, .T.);
|
||||
#366 = ORIENTED_EDGE('NONE', *, *, #264, .T.);
|
||||
#367 = ORIENTED_EDGE('NONE', *, *, #267, .T.);
|
||||
#368 = ORIENTED_EDGE('NONE', *, *, #269, .T.);
|
||||
#369 = EDGE_LOOP('NONE', (#355, #356, #357, #358, #359, #360, #361, #362, #363, #364, #365, #366, #367, #368));
|
||||
#370 = CARTESIAN_POINT('NONE', (0, -0.3225799999999985, 1.2903199999999995));
|
||||
#371 = DIRECTION('NONE', (-1, -0, 0));
|
||||
#372 = AXIS2_PLACEMENT_3D('NONE', #370, #371, $);
|
||||
#373 = PLANE('NONE', #372);
|
||||
#374 = CARTESIAN_POINT('NONE', (0.9983910612778368, -0.6451599999999998, 1.2903199999999997));
|
||||
#375 = DIRECTION('NONE', (0, -1, 0));
|
||||
#376 = AXIS2_PLACEMENT_3D('NONE', #374, #375, $);
|
||||
#377 = PLANE('NONE', #376);
|
||||
#378 = CARTESIAN_POINT('NONE', (2.918166090585415, -1.2903199999999988, 1.2903199999999997));
|
||||
#379 = DIRECTION('NONE', (-0.5735764363510459, -0.8191520442889919, 0));
|
||||
#380 = AXIS2_PLACEMENT_3D('NONE', #378, #379, $);
|
||||
#381 = PLANE('NONE', #380);
|
||||
#382 = CARTESIAN_POINT('NONE', (4.984285029307579, -1.9354799999999992, 1.2903199999999997));
|
||||
#383 = DIRECTION('NONE', (0, -1, 0));
|
||||
#384 = AXIS2_PLACEMENT_3D('NONE', #382, #383, $);
|
||||
#385 = PLANE('NONE', #384);
|
||||
#386 = CARTESIAN_POINT('NONE', (6.129019999999999, -1.7741899999999997, 1.2903199999999997));
|
||||
#387 = DIRECTION('NONE', (1, -0, 0));
|
||||
#388 = AXIS2_PLACEMENT_3D('NONE', #386, #387, $);
|
||||
#389 = PLANE('NONE', #388);
|
||||
#390 = CARTESIAN_POINT('NONE', (5.035139570965871, -1.6128999999999998, 1.2903199999999997));
|
||||
#391 = DIRECTION('NONE', (0, 1, -0));
|
||||
#392 = AXIS2_PLACEMENT_3D('NONE', #390, #391, $);
|
||||
#393 = PLANE('NONE', #392);
|
||||
#394 = CARTESIAN_POINT('NONE', (2.7895291818945633, -0.8064499999999998, 1.2903199999999995));
|
||||
#395 = DIRECTION('NONE', (0.5735764363510459, 0.8191520442889918, -0));
|
||||
#396 = AXIS2_PLACEMENT_3D('NONE', #394, #395, $);
|
||||
#397 = PLANE('NONE', #396);
|
||||
#398 = CARTESIAN_POINT('NONE', (2.6754617854843468, 0.4838700000000003, 1.2903199999999997));
|
||||
#399 = DIRECTION('NONE', (0.4226182617406992, -0.90630778703665, 0));
|
||||
#400 = AXIS2_PLACEMENT_3D('NONE', #398, #399, $);
|
||||
#401 = PLANE('NONE', #400);
|
||||
#402 = CARTESIAN_POINT('NONE', (4.921072174555653, 0.9677399999999998, 1.2903199999999995));
|
||||
#403 = DIRECTION('NONE', (0, -1, 0));
|
||||
#404 = AXIS2_PLACEMENT_3D('NONE', #402, #403, $);
|
||||
#405 = PLANE('NONE', #404);
|
||||
#406 = CARTESIAN_POINT('NONE', (6.129019999999998, 1.1290299999999989, 1.2903199999999995));
|
||||
#407 = DIRECTION('NONE', (1, -0, 0));
|
||||
#408 = AXIS2_PLACEMENT_3D('NONE', #406, #407, $);
|
||||
#409 = PLANE('NONE', #408);
|
||||
#410 = CARTESIAN_POINT('NONE', (4.8853150424179725, 1.2903199999999997, 1.2903199999999997));
|
||||
#411 = DIRECTION('NONE', (0, 1, -0));
|
||||
#412 = AXIS2_PLACEMENT_3D('NONE', #410, #411, $);
|
||||
#413 = PLANE('NONE', #412);
|
||||
#414 = CARTESIAN_POINT('NONE', (2.9498350424179733, 0.9677399999999998, 1.2903199999999997));
|
||||
#415 = DIRECTION('NONE', (-0.42261826174069933, 0.9063077870366499, -0));
|
||||
#416 = AXIS2_PLACEMENT_3D('NONE', #414, #415, $);
|
||||
#417 = PLANE('NONE', #416);
|
||||
#418 = CARTESIAN_POINT('NONE', (1.1290299999999998, 0.6451599999999998, 1.29032));
|
||||
#419 = DIRECTION('NONE', (0, 1, -0));
|
||||
#420 = AXIS2_PLACEMENT_3D('NONE', #418, #419, $);
|
||||
#421 = PLANE('NONE', #420);
|
||||
#422 = CARTESIAN_POINT('NONE', (0, 0.32257999999999987, 1.2903199999999995));
|
||||
#423 = DIRECTION('NONE', (-1, -0, 0));
|
||||
#424 = AXIS2_PLACEMENT_3D('NONE', #422, #423, $);
|
||||
#425 = PLANE('NONE', #424);
|
||||
#426 = CARTESIAN_POINT('NONE', (0, 0, -0));
|
||||
#427 = DIRECTION('NONE', (0, 0, 1));
|
||||
#428 = AXIS2_PLACEMENT_3D('NONE', #426, #427, $);
|
||||
#429 = PLANE('NONE', #428);
|
||||
#430 = CARTESIAN_POINT('NONE', (0, 0, 2.58064));
|
||||
#431 = DIRECTION('NONE', (0, 0, 1));
|
||||
#432 = AXIS2_PLACEMENT_3D('NONE', #430, #431, $);
|
||||
#433 = PLANE('NONE', #432);
|
||||
#434 = FACE_OUTER_BOUND('NONE', #274, .T.);
|
||||
#435 = ADVANCED_FACE('NONE', (#434), #373, .T.);
|
||||
#436 = FACE_OUTER_BOUND('NONE', #279, .T.);
|
||||
#437 = ADVANCED_FACE('NONE', (#436), #377, .T.);
|
||||
#438 = FACE_OUTER_BOUND('NONE', #284, .T.);
|
||||
#439 = ADVANCED_FACE('NONE', (#438), #381, .T.);
|
||||
#440 = FACE_OUTER_BOUND('NONE', #289, .T.);
|
||||
#441 = ADVANCED_FACE('NONE', (#440), #385, .T.);
|
||||
#442 = FACE_OUTER_BOUND('NONE', #294, .T.);
|
||||
#443 = ADVANCED_FACE('NONE', (#442), #389, .T.);
|
||||
#444 = FACE_OUTER_BOUND('NONE', #299, .T.);
|
||||
#445 = ADVANCED_FACE('NONE', (#444), #393, .T.);
|
||||
#446 = FACE_OUTER_BOUND('NONE', #304, .T.);
|
||||
#447 = ADVANCED_FACE('NONE', (#446), #397, .T.);
|
||||
#448 = FACE_OUTER_BOUND('NONE', #309, .T.);
|
||||
#449 = ADVANCED_FACE('NONE', (#448), #401, .T.);
|
||||
#450 = FACE_OUTER_BOUND('NONE', #314, .T.);
|
||||
#451 = ADVANCED_FACE('NONE', (#450), #405, .T.);
|
||||
#452 = FACE_OUTER_BOUND('NONE', #319, .T.);
|
||||
#453 = ADVANCED_FACE('NONE', (#452), #409, .T.);
|
||||
#454 = FACE_OUTER_BOUND('NONE', #324, .T.);
|
||||
#455 = ADVANCED_FACE('NONE', (#454), #413, .T.);
|
||||
#456 = FACE_OUTER_BOUND('NONE', #329, .T.);
|
||||
#457 = ADVANCED_FACE('NONE', (#456), #417, .T.);
|
||||
#458 = FACE_OUTER_BOUND('NONE', #334, .T.);
|
||||
#459 = ADVANCED_FACE('NONE', (#458), #421, .T.);
|
||||
#460 = FACE_OUTER_BOUND('NONE', #339, .T.);
|
||||
#461 = ADVANCED_FACE('NONE', (#460), #425, .T.);
|
||||
#462 = FACE_OUTER_BOUND('NONE', #354, .F.);
|
||||
#463 = ADVANCED_FACE('NONE', (#462), #429, .F.);
|
||||
#464 = FACE_OUTER_BOUND('NONE', #369, .T.);
|
||||
#465 = ADVANCED_FACE('NONE', (#464), #433, .T.);
|
||||
#466 = CLOSED_SHELL('NONE', (#435, #437, #439, #441, #443, #445, #447, #449, #451, #453, #455, #457, #459, #461, #463, #465));
|
||||
#467 = ORIENTED_CLOSED_SHELL('NONE', *, #466, .T.);
|
||||
#468 = MANIFOLD_SOLID_BREP('NONE', #467);
|
||||
#469 = APPLICATION_CONTEXT('configuration controlled 3D design of mechanical parts and assemblies');
|
||||
#470 = PRODUCT_DEFINITION_CONTEXT('part definition', #469, 'design');
|
||||
#471 = PRODUCT('UNIDENTIFIED_PRODUCT', 'NONE', $, ());
|
||||
#472 = PRODUCT_DEFINITION_FORMATION('', $, #471);
|
||||
#473 = PRODUCT_DEFINITION('design', $, #472, #470);
|
||||
#474 = PRODUCT_DEFINITION_SHAPE('NONE', $, #473);
|
||||
#475 = ADVANCED_BREP_SHAPE_REPRESENTATION('NONE', (#468), #3);
|
||||
#476 = SHAPE_DEFINITION_REPRESENTATION(#474, #475);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
BIN
e2e/playwright/export-snapshots/stl-ascii.png
Normal file
After Width: | Height: | Size: 221 KiB |
478
e2e/playwright/export-snapshots/stl-ascii.stl
Normal file
@ -0,0 +1,478 @@
|
||||
solid unnamed
|
||||
facet normal -1 0 0
|
||||
outer loop
|
||||
vertex 0 -101.600006 0
|
||||
vertex 0 -0 0
|
||||
vertex 0 -101.600006 -25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -1 0 0
|
||||
outer loop
|
||||
vertex 0 -101.600006 -25.400002
|
||||
vertex 0 -0 0
|
||||
vertex 0 -0 -25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 0 -101.600006 -25.400002
|
||||
vertex 0 -0 -25.400002
|
||||
vertex 78.613464 -101.600006 -25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 78.613464 -101.600006 -25.400002
|
||||
vertex 0 -0 -25.400002
|
||||
vertex 78.613464 -0 -25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0.5735764 0 -0.8191522
|
||||
outer loop
|
||||
vertex 78.613464 -101.600006 -25.400002
|
||||
vertex 78.613464 -0 -25.400002
|
||||
vertex 151.16339 -101.600006 -76.2
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0.5735764 0 -0.8191522
|
||||
outer loop
|
||||
vertex 151.16339 -101.600006 -76.2
|
||||
vertex 78.613464 -0 -25.400002
|
||||
vertex 151.16339 -0 -76.2
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 151.16339 -101.600006 -76.2
|
||||
vertex 151.16339 -0 -76.2
|
||||
vertex 241.3 -101.600006 -76.2
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 -76.2
|
||||
vertex 151.16339 -0 -76.2
|
||||
vertex 241.3 -0 -76.2
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 1 0 0
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 -76.2
|
||||
vertex 241.3 -0 -76.2
|
||||
vertex 241.3 -101.600006 -63.5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 1 -0 0
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 -63.5
|
||||
vertex 241.3 -0 -76.2
|
||||
vertex 241.3 -0 -63.5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -0 1
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 -63.5
|
||||
vertex 241.3 -0 -63.5
|
||||
vertex 155.16768 -101.600006 -63.5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 1
|
||||
outer loop
|
||||
vertex 155.16768 -101.600006 -63.5
|
||||
vertex 241.3 -0 -63.5
|
||||
vertex 155.16768 -0 -63.5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.5735765 0 0.81915194
|
||||
outer loop
|
||||
vertex 87.15214 -101.600006 -15.875
|
||||
vertex 109.82398 -101.600006 -31.75
|
||||
vertex 109.82398 -0 -31.75
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.57357645 0 0.819152
|
||||
outer loop
|
||||
vertex 109.82398 -101.600006 -31.75
|
||||
vertex 155.16768 -101.600006 -63.5
|
||||
vertex 155.16768 -0 -63.5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.57357645 0 0.81915206
|
||||
outer loop
|
||||
vertex 87.15214 -0 -15.875
|
||||
vertex 64.480286 -101.600006 0
|
||||
vertex 87.15214 -101.600006 -15.875
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.5735765 0 0.81915194
|
||||
outer loop
|
||||
vertex 109.82398 -0 -31.75
|
||||
vertex 87.15214 -0 -15.875
|
||||
vertex 87.15214 -101.600006 -15.875
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.57357645 -0 0.819152
|
||||
outer loop
|
||||
vertex 109.82398 -101.600006 -31.75
|
||||
vertex 155.16768 -0 -63.5
|
||||
vertex 109.82398 -0 -31.75
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.57357645 -0 0.81915206
|
||||
outer loop
|
||||
vertex 64.480286 -101.600006 0
|
||||
vertex 87.15214 -0 -15.875
|
||||
vertex 64.480286 -0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.4226182 0 -0.9063078
|
||||
outer loop
|
||||
vertex 84.906715 -101.600006 9.525
|
||||
vertex 64.480286 -101.600006 0
|
||||
vertex 64.480286 -0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.42261833 0 -0.90630776
|
||||
outer loop
|
||||
vertex 105.33314 -101.600006 19.05
|
||||
vertex 84.906715 -101.600006 9.525
|
||||
vertex 84.906715 -0 9.525
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.4226182 0 -0.9063078
|
||||
outer loop
|
||||
vertex 84.906715 -0 9.525
|
||||
vertex 84.906715 -101.600006 9.525
|
||||
vertex 64.480286 -0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.4226183 0 -0.9063078
|
||||
outer loop
|
||||
vertex 105.33314 -0 19.05
|
||||
vertex 146.18599 -101.600006 38.1
|
||||
vertex 105.33314 -101.600006 19.05
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.42261833 0 -0.90630776
|
||||
outer loop
|
||||
vertex 105.33314 -101.600006 19.05
|
||||
vertex 84.906715 -0 9.525
|
||||
vertex 105.33314 -0 19.05
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0.4226183 0 -0.9063078
|
||||
outer loop
|
||||
vertex 146.18599 -101.600006 38.1
|
||||
vertex 105.33314 -0 19.05
|
||||
vertex 146.18599 -0 38.1
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 146.18599 -101.600006 38.1
|
||||
vertex 146.18599 -0 38.1
|
||||
vertex 241.3 -101.600006 38.1
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 38.1
|
||||
vertex 146.18599 -0 38.1
|
||||
vertex 241.3 -0 38.1
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 1 0 0
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 38.1
|
||||
vertex 241.3 -0 38.1
|
||||
vertex 241.3 -101.600006 50.800003
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 1 -0 0
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 50.800003
|
||||
vertex 241.3 -0 38.1
|
||||
vertex 241.3 -0 50.800003
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -0 0.99999994
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 50.800003
|
||||
vertex 241.3 -0 50.800003
|
||||
vertex 143.37048 -101.600006 50.800003
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 0.99999994
|
||||
outer loop
|
||||
vertex 143.37048 -101.600006 50.800003
|
||||
vertex 241.3 -0 50.800003
|
||||
vertex 143.37048 -0 50.800003
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0.42261827 0 0.9063078
|
||||
outer loop
|
||||
vertex 143.37048 -101.600006 50.800003
|
||||
vertex 143.37048 -0 50.800003
|
||||
vertex 88.9 -101.600006 25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0.42261827 0 0.9063078
|
||||
outer loop
|
||||
vertex 88.9 -101.600006 25.400002
|
||||
vertex 143.37048 -0 50.800003
|
||||
vertex 88.9 -0 25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -0 1
|
||||
outer loop
|
||||
vertex 88.9 -101.600006 25.400002
|
||||
vertex 88.9 -0 25.400002
|
||||
vertex 0 -101.600006 25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 1
|
||||
outer loop
|
||||
vertex 0 -101.600006 25.400002
|
||||
vertex 88.9 -0 25.400002
|
||||
vertex 0 -0 25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -1 0 0
|
||||
outer loop
|
||||
vertex 0 -101.600006 25.400002
|
||||
vertex 0 -0 25.400002
|
||||
vertex 0 -101.600006 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -1 0 0
|
||||
outer loop
|
||||
vertex 0 -101.600006 0
|
||||
vertex 0 -0 25.400002
|
||||
vertex 0 -0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 -0
|
||||
outer loop
|
||||
vertex 84.906715 -0 9.525
|
||||
vertex 64.480286 -0 0
|
||||
vertex 88.9 -0 25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 105.33314 -0 19.05
|
||||
vertex 84.906715 -0 9.525
|
||||
vertex 88.9 -0 25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 87.15214 -0 -15.875
|
||||
vertex 109.82398 -0 -31.75
|
||||
vertex 78.613464 -0 -25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 105.33314 -0 19.05
|
||||
vertex 143.37048 -0 50.800003
|
||||
vertex 146.18599 -0 38.1
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 1 0
|
||||
outer loop
|
||||
vertex 0 -0 25.400002
|
||||
vertex 88.9 -0 25.400002
|
||||
vertex 64.480286 -0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 0 -0 25.400002
|
||||
vertex 64.480286 -0 0
|
||||
vertex 0 -0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 1 0
|
||||
outer loop
|
||||
vertex 143.37048 -0 50.800003
|
||||
vertex 241.3 -0 50.800003
|
||||
vertex 146.18599 -0 38.1
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 241.3 -0 50.800003
|
||||
vertex 241.3 -0 38.1
|
||||
vertex 146.18599 -0 38.1
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 -0
|
||||
outer loop
|
||||
vertex 105.33314 -0 19.05
|
||||
vertex 88.9 -0 25.400002
|
||||
vertex 143.37048 -0 50.800003
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0.99999994 0
|
||||
outer loop
|
||||
vertex 64.480286 -0 0
|
||||
vertex 87.15214 -0 -15.875
|
||||
vertex 78.613464 -0 -25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 109.82398 -0 -31.75
|
||||
vertex 151.16339 -0 -76.2
|
||||
vertex 78.613464 -0 -25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 155.16768 -0 -63.5
|
||||
vertex 151.16339 -0 -76.2
|
||||
vertex 109.82398 -0 -31.75
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 241.3 -0 -63.5
|
||||
vertex 241.3 -0 -76.2
|
||||
vertex 155.16768 -0 -63.5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 155.16768 -0 -63.5
|
||||
vertex 241.3 -0 -76.2
|
||||
vertex 151.16339 -0 -76.2
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 64.480286 -0 0
|
||||
vertex 78.613464 -0 -25.400002
|
||||
vertex 0 -0 -25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 0 -0 -25.400002
|
||||
vertex 0 -0 0
|
||||
vertex 64.480286 -0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 -1 0
|
||||
outer loop
|
||||
vertex 84.906715 -101.600006 9.525
|
||||
vertex 88.9 -101.600006 25.400002
|
||||
vertex 64.480286 -101.600006 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 -1 0
|
||||
outer loop
|
||||
vertex 105.33314 -101.600006 19.05
|
||||
vertex 88.9 -101.600006 25.400002
|
||||
vertex 84.906715 -101.600006 9.525
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 -0
|
||||
outer loop
|
||||
vertex 87.15214 -101.600006 -15.875
|
||||
vertex 78.613464 -101.600006 -25.400002
|
||||
vertex 109.82398 -101.600006 -31.75
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 0
|
||||
outer loop
|
||||
vertex 105.33314 -101.600006 19.05
|
||||
vertex 146.18599 -101.600006 38.1
|
||||
vertex 143.37048 -101.600006 50.800003
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 0
|
||||
outer loop
|
||||
vertex 0 -101.600006 25.400002
|
||||
vertex 64.480286 -101.600006 0
|
||||
vertex 88.9 -101.600006 25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 0
|
||||
outer loop
|
||||
vertex 0 -101.600006 25.400002
|
||||
vertex 0 -101.600006 0
|
||||
vertex 64.480286 -101.600006 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 0
|
||||
outer loop
|
||||
vertex 143.37048 -101.600006 50.800003
|
||||
vertex 146.18599 -101.600006 38.1
|
||||
vertex 241.3 -101.600006 50.800003
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 -0
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 50.800003
|
||||
vertex 146.18599 -101.600006 38.1
|
||||
vertex 241.3 -101.600006 38.1
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 0
|
||||
outer loop
|
||||
vertex 105.33314 -101.600006 19.05
|
||||
vertex 143.37048 -101.600006 50.800003
|
||||
vertex 88.9 -101.600006 25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -0.99999994 0
|
||||
outer loop
|
||||
vertex 64.480286 -101.600006 0
|
||||
vertex 78.613464 -101.600006 -25.400002
|
||||
vertex 87.15214 -101.600006 -15.875
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 -1 -0
|
||||
outer loop
|
||||
vertex 109.82398 -101.600006 -31.75
|
||||
vertex 78.613464 -101.600006 -25.400002
|
||||
vertex 151.16339 -101.600006 -76.2
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 -1 0
|
||||
outer loop
|
||||
vertex 155.16768 -101.600006 -63.5
|
||||
vertex 109.82398 -101.600006 -31.75
|
||||
vertex 151.16339 -101.600006 -76.2
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 -1 -0
|
||||
outer loop
|
||||
vertex 241.3 -101.600006 -63.5
|
||||
vertex 155.16768 -101.600006 -63.5
|
||||
vertex 241.3 -101.600006 -76.2
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 -0
|
||||
outer loop
|
||||
vertex 155.16768 -101.600006 -63.5
|
||||
vertex 151.16339 -101.600006 -76.2
|
||||
vertex 241.3 -101.600006 -76.2
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 -0
|
||||
outer loop
|
||||
vertex 64.480286 -101.600006 0
|
||||
vertex 0 -101.600006 -25.400002
|
||||
vertex 78.613464 -101.600006 -25.400002
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 0
|
||||
outer loop
|
||||
vertex 0 -101.600006 -25.400002
|
||||
vertex 64.480286 -101.600006 0
|
||||
vertex 0 -101.600006 0
|
||||
endloop
|
||||
endfacet
|
||||
endsolid unnamed
|
BIN
e2e/playwright/export-snapshots/stl-binary.png
Normal file
After Width: | Height: | Size: 221 KiB |
BIN
e2e/playwright/export-snapshots/stl-binary.stl
Normal file
1052
e2e/playwright/flow-tests.spec.ts
Normal file
21
e2e/playwright/secrets.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
const secrets: Record<string, string> = {}
|
||||
try {
|
||||
const file = readFileSync('./e2e/playwright/playwright-secrets.env', 'utf8')
|
||||
file
|
||||
.split('\n')
|
||||
.filter((line) => line && line.length > 1)
|
||||
.forEach((line) => {
|
||||
const [key, value] = line.split('=')
|
||||
// prefer env vars over secrets file
|
||||
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
|
||||
})
|
||||
} catch (err) {
|
||||
// probably running in CI
|
||||
secrets.token = process.env.token || ''
|
||||
secrets.snapshottoken = process.env.snapshottoken || ''
|
||||
// add more env vars here to make them available in CI
|
||||
}
|
||||
|
||||
export { secrets }
|
495
e2e/playwright/snapshot-tests.spec.ts
Normal file
@ -0,0 +1,495 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { secrets } from './secrets'
|
||||
import { getUtils } from './test-utils'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import fsp from 'fs/promises'
|
||||
import { spawn } from 'child_process'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await context.addInitScript(async (token) => {
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||
localStorage.setItem('persistCode', ``)
|
||||
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',
|
||||
})
|
||||
)
|
||||
}, secrets.token)
|
||||
// reducedMotion kills animations, which speeds up tests and reduces flakiness
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
})
|
||||
|
||||
test.setTimeout(60000)
|
||||
|
||||
const commonPoints = {
|
||||
startAt: '[26.38, -35.59]',
|
||||
num1: 26.63,
|
||||
num2: 53.01,
|
||||
}
|
||||
|
||||
test('change camera, show planes', async ({ page, context }) => {
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
const camPos: [number, number, number] = [0, 85, 85]
|
||||
await u.updateCamPosition(camPos)
|
||||
|
||||
// rotate
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.move(700, 200)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(600, 300)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
|
||||
await u.openDebugPanel()
|
||||
await page.waitForTimeout(500)
|
||||
await u.clearCommandLogs()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
await u.updateCamPosition(camPos)
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await u.closeDebugPanel()
|
||||
// pan
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.move(600, 200)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(700, 200)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
await u.openDebugPanel()
|
||||
await page.waitForTimeout(300)
|
||||
await u.clearCommandLogs()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
await u.updateCamPosition(camPos)
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// zoom
|
||||
await page.keyboard.down('Control')
|
||||
await page.mouse.move(700, 400)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(700, 300)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
await u.openDebugPanel()
|
||||
await page.waitForTimeout(300)
|
||||
await u.clearCommandLogs()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test('exports of each format should work', async ({ page, context }) => {
|
||||
// FYI this test doesn't work with only engine running locally
|
||||
// And you will need to have the KittyCAD CLI installed
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const topAng = 25
|
||||
const bottomAng = 35
|
||||
const baseLen = 3.5
|
||||
const baseHeight = 1
|
||||
const totalHeightHalf = 2
|
||||
const armThick = 0.5
|
||||
const totalLen = 9.5
|
||||
const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> yLine(baseHeight, %)
|
||||
|> xLine(baseLen, %)
|
||||
|> angledLineToY({
|
||||
angle: topAng,
|
||||
to: totalHeightHalf,
|
||||
tag: 'seg04'
|
||||
}, %)
|
||||
|> xLineTo({ to: totalLen, tag: 'seg03' }, %)
|
||||
|> yLine({ length: -armThick, tag: 'seg01' }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle: HALF_TURN,
|
||||
offset: -armThick,
|
||||
intersectTag: 'seg04'
|
||||
}, %)
|
||||
|> angledLineToY([segAng('seg04', %) + 180, ZERO], %)
|
||||
|> angledLineToY({
|
||||
angle: -bottomAng,
|
||||
to: -totalHeightHalf - armThick,
|
||||
tag: 'seg02'
|
||||
}, %)
|
||||
|> xLineTo(segEndX('seg03', %) + 0, %)
|
||||
|> yLine(-segLen('seg01', %), %)
|
||||
|> angledLineThatIntersects({
|
||||
angle: HALF_TURN,
|
||||
offset: -armThick,
|
||||
intersectTag: 'seg02'
|
||||
}, %)
|
||||
|> angledLineToY([segAng('seg02', %) + 180, -baseHeight], %)
|
||||
|> xLineTo(ZERO, %)
|
||||
|> close(%)
|
||||
|> extrude(4, %)`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.waitForCmdReceive('extrude')
|
||||
await page.waitForTimeout(1000)
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
await page.getByRole('button', { name: APP_NAME }).click()
|
||||
|
||||
interface Paths {
|
||||
modelPath: string
|
||||
imagePath: string
|
||||
outputType: string
|
||||
}
|
||||
const doExport = async (
|
||||
output: Models['OutputFormat_type']
|
||||
): Promise<Paths> => {
|
||||
await page.getByRole('button', { name: 'Export Model' }).click()
|
||||
|
||||
const exportSelect = page.getByTestId('export-type')
|
||||
await exportSelect.selectOption({ label: output.type })
|
||||
|
||||
if ('storage' in output) {
|
||||
const storageSelect = page.getByTestId('export-storage')
|
||||
await storageSelect.selectOption({ label: output.storage })
|
||||
}
|
||||
|
||||
const downloadPromise = page.waitForEvent('download')
|
||||
await page.getByRole('button', { name: 'Export', exact: true }).click()
|
||||
const download = await downloadPromise
|
||||
const downloadLocationer = (extra = '', isImage = false) =>
|
||||
`./e2e/playwright/export-snapshots/${output.type}-${
|
||||
'storage' in output ? output.storage : ''
|
||||
}${extra}.${isImage ? 'png' : output.type}`
|
||||
const downloadLocation = downloadLocationer()
|
||||
const downloadLocation2 = downloadLocationer('-2')
|
||||
|
||||
if (output.type === 'gltf' && output.storage === 'standard') {
|
||||
// wait for second download
|
||||
const download2 = await page.waitForEvent('download')
|
||||
await download.saveAs(downloadLocation)
|
||||
await download2.saveAs(downloadLocation2)
|
||||
|
||||
// rewrite uri to reference our file name
|
||||
const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
|
||||
const isJson = fileContents.includes('buffers')
|
||||
let contents = fileContents
|
||||
let reWriteLocation = downloadLocation
|
||||
let uri = downloadLocation2.split('/').pop()
|
||||
if (!isJson) {
|
||||
contents = await fsp.readFile(downloadLocation2, 'utf-8')
|
||||
reWriteLocation = downloadLocation2
|
||||
uri = downloadLocation.split('/').pop()
|
||||
}
|
||||
contents = contents.replace(/"uri": ".*"/g, `"uri": "${uri}"`)
|
||||
await fsp.writeFile(reWriteLocation, contents)
|
||||
} else {
|
||||
await download.saveAs(downloadLocation)
|
||||
}
|
||||
|
||||
if (output.type === 'step') {
|
||||
// stable timestamps for step files
|
||||
const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
|
||||
const newFileContents = fileContents.replace(
|
||||
/[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+[0-9]+[0-9]\+[0-9]{2}:[0-9]{2}/g,
|
||||
'1970-01-01T00:00:00.0+00:00'
|
||||
)
|
||||
await fsp.writeFile(downloadLocation, newFileContents)
|
||||
}
|
||||
return {
|
||||
modelPath: downloadLocation,
|
||||
imagePath: downloadLocationer('', true),
|
||||
outputType: output.type,
|
||||
}
|
||||
}
|
||||
const axisDirectionPair: Models['AxisDirectionPair_type'] = {
|
||||
axis: 'z',
|
||||
direction: 'positive',
|
||||
}
|
||||
const sysType: Models['System_type'] = {
|
||||
forward: axisDirectionPair,
|
||||
up: axisDirectionPair,
|
||||
}
|
||||
|
||||
const exportLocations: Paths[] = []
|
||||
|
||||
// NOTE it was easiest to leverage existing types and have doExport take Models['OutputFormat_type'] as in input
|
||||
// just note that only `type` and `storage` are used for selecting the drop downs is the app
|
||||
// the rest are only there to make typescript happy
|
||||
exportLocations.push(
|
||||
await doExport({
|
||||
type: 'step',
|
||||
coords: sysType,
|
||||
})
|
||||
)
|
||||
exportLocations.push(
|
||||
await doExport({
|
||||
type: 'ply',
|
||||
coords: sysType,
|
||||
selection: { type: 'default_scene' },
|
||||
storage: 'ascii',
|
||||
units: 'in',
|
||||
})
|
||||
)
|
||||
exportLocations.push(
|
||||
await doExport({
|
||||
type: 'ply',
|
||||
storage: 'binary_little_endian',
|
||||
coords: sysType,
|
||||
selection: { type: 'default_scene' },
|
||||
units: 'in',
|
||||
})
|
||||
)
|
||||
exportLocations.push(
|
||||
await doExport({
|
||||
type: 'ply',
|
||||
storage: 'binary_big_endian',
|
||||
coords: sysType,
|
||||
selection: { type: 'default_scene' },
|
||||
units: 'in',
|
||||
})
|
||||
)
|
||||
exportLocations.push(
|
||||
await doExport({
|
||||
type: 'stl',
|
||||
storage: 'ascii',
|
||||
coords: sysType,
|
||||
units: 'in',
|
||||
selection: { type: 'default_scene' },
|
||||
})
|
||||
)
|
||||
exportLocations.push(
|
||||
await doExport({
|
||||
type: 'stl',
|
||||
storage: 'binary',
|
||||
coords: sysType,
|
||||
units: 'in',
|
||||
selection: { type: 'default_scene' },
|
||||
})
|
||||
)
|
||||
exportLocations.push(
|
||||
await doExport({
|
||||
// obj seems to be a little flaky, times out tests sometimes
|
||||
type: 'obj',
|
||||
coords: sysType,
|
||||
units: 'in',
|
||||
})
|
||||
)
|
||||
exportLocations.push(
|
||||
await doExport({
|
||||
type: 'gltf',
|
||||
storage: 'embedded',
|
||||
presentation: 'pretty',
|
||||
})
|
||||
)
|
||||
exportLocations.push(
|
||||
await doExport({
|
||||
type: 'gltf',
|
||||
storage: 'binary',
|
||||
presentation: 'pretty',
|
||||
})
|
||||
)
|
||||
|
||||
// TODO: gltfs don't seem to work with snap shots. push onto exportLocations once it's figured out
|
||||
await doExport({
|
||||
type: 'gltf',
|
||||
storage: 'standard',
|
||||
presentation: 'pretty',
|
||||
})
|
||||
|
||||
// close page to disconnect websocket since we can only have one open atm
|
||||
await page.close()
|
||||
|
||||
// snapshot exports, good compromise to capture that exports are healthy without getting bogged down in "did the formatting change" changes
|
||||
// context: https://github.com/KittyCAD/modeling-app/issues/1222
|
||||
for (const { modelPath, imagePath, outputType } of exportLocations) {
|
||||
console.log(
|
||||
`taking snapshot of using: "zoo file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}"`
|
||||
)
|
||||
const cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}`
|
||||
const child = spawn(cliCommand, { shell: true })
|
||||
const result = await new Promise<string>((resolve, reject) => {
|
||||
child.on('error', (code: any, msg: any) => {
|
||||
console.log('error', code, msg)
|
||||
reject('error')
|
||||
})
|
||||
child.on('exit', (code, msg) => {
|
||||
console.log('exit', code, msg)
|
||||
if (code !== 0) {
|
||||
reject(`exit code ${code} for model ${modelPath}`)
|
||||
} else {
|
||||
resolve('success')
|
||||
}
|
||||
})
|
||||
child.stderr.on('data', (data) => console.log(`stderr: ${data}`))
|
||||
child.stdout.on('data', (data) => console.log(`stdout: ${data}`))
|
||||
})
|
||||
expect(result).toBe('success')
|
||||
if (result === 'success') {
|
||||
console.log(`snapshot taken for ${modelPath}`)
|
||||
} else {
|
||||
console.log(`snapshot failed for ${modelPath}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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([0.70, 0.44], %)
|
||||
|> line([0.66, -0.02], %)
|
||||
|> line([0.28, 0.50], %)
|
||||
|> line([-0.56, 0.44], %)
|
||||
|> line([-0.54, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(1.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 }) => {
|
||||
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(${commonPoints.startAt}, %)`)
|
||||
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(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 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,
|
||||
})
|
||||
})
|
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 52 KiB |
167
e2e/playwright/test-utils.ts
Normal file
@ -0,0 +1,167 @@
|
||||
import { expect, Page } from '@playwright/test'
|
||||
import { EngineCommand } from '../../src/lang/std/engineConnection'
|
||||
import fsp from 'fs/promises'
|
||||
import pixelMatch from 'pixelmatch'
|
||||
import { PNG } from 'pngjs'
|
||||
|
||||
async function waitForPageLoad(page: Page) {
|
||||
// wait for 'Loading stream...' spinner
|
||||
await page.getByTestId('loading-stream').waitFor()
|
||||
// wait for all spinners to be gone
|
||||
await page.getByTestId('loading').waitFor({ state: 'detached' })
|
||||
|
||||
await page.getByTestId('start-sketch').waitFor()
|
||||
}
|
||||
|
||||
async function removeCurrentCode(page: Page) {
|
||||
const hotkey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
await page.click('.cm-content')
|
||||
await page.keyboard.down(hotkey)
|
||||
await page.keyboard.press('a')
|
||||
await page.keyboard.up(hotkey)
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-content')).toHaveText('')
|
||||
}
|
||||
|
||||
async function sendCustomCmd(page: Page, cmd: EngineCommand) {
|
||||
await page.fill('[data-testid="custom-cmd-input"]', JSON.stringify(cmd))
|
||||
await page.click('[data-testid="custom-cmd-send-button"]')
|
||||
}
|
||||
|
||||
async function clearCommandLogs(page: Page) {
|
||||
await page.click('[data-testid="clear-commands"]')
|
||||
}
|
||||
|
||||
async function expectCmdLog(page: Page, locatorStr: string) {
|
||||
await expect(page.locator(locatorStr)).toBeVisible()
|
||||
}
|
||||
|
||||
async function waitForDefaultPlanesToBeVisible(page: Page) {
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
document.querySelectorAll('[data-receive-command-type="object_visible"]')
|
||||
.length >= 3
|
||||
)
|
||||
}
|
||||
|
||||
async function openDebugPanel(page: Page) {
|
||||
const isOpen =
|
||||
(await page
|
||||
.locator('[data-testid="debug-panel"]')
|
||||
?.getAttribute('open')) === ''
|
||||
|
||||
if (!isOpen) {
|
||||
await page.getByText('Debug').click()
|
||||
await page.getByTestId('debug-panel').and(page.locator('[open]')).waitFor()
|
||||
}
|
||||
}
|
||||
|
||||
async function closeDebugPanel(page: Page) {
|
||||
const isOpen =
|
||||
(await page.getByTestId('debug-panel')?.getAttribute('open')) === ''
|
||||
if (isOpen) {
|
||||
await page.getByText('Debug').click()
|
||||
await page
|
||||
.getByTestId('debug-panel')
|
||||
.and(page.locator(':not([open])'))
|
||||
.waitFor()
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForCmdReceive(page: Page, commandType: string) {
|
||||
return page
|
||||
.locator(`[data-receive-command-type="${commandType}"]`)
|
||||
.first()
|
||||
.waitFor()
|
||||
}
|
||||
|
||||
export function getUtils(page: Page) {
|
||||
return {
|
||||
waitForAuthSkipAppStart: () => waitForPageLoad(page),
|
||||
removeCurrentCode: () => removeCurrentCode(page),
|
||||
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),
|
||||
expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr),
|
||||
openDebugPanel: () => openDebugPanel(page),
|
||||
closeDebugPanel: () => closeDebugPanel(page),
|
||||
openAndClearDebugPanel: async () => {
|
||||
await openDebugPanel(page)
|
||||
return clearCommandLogs(page)
|
||||
},
|
||||
clearAndCloseDebugPanel: async () => {
|
||||
await clearCommandLogs(page)
|
||||
return closeDebugPanel(page)
|
||||
},
|
||||
waitForCmdReceive: (commandType: string) =>
|
||||
waitForCmdReceive(page, commandType),
|
||||
doAndWaitForCmd: async (
|
||||
fn: () => Promise<void>,
|
||||
commandType: string,
|
||||
endWithDebugPanelOpen = true
|
||||
) => {
|
||||
await openDebugPanel(page)
|
||||
await clearCommandLogs(page)
|
||||
await closeDebugPanel(page)
|
||||
await fn()
|
||||
await openDebugPanel(page)
|
||||
await waitForCmdReceive(page, commandType)
|
||||
if (!endWithDebugPanelOpen) {
|
||||
await closeDebugPanel(page)
|
||||
}
|
||||
},
|
||||
doAndWaitForImageDiff: (fn: () => Promise<any>, diffCount = 200) =>
|
||||
new Promise(async (resolve) => {
|
||||
await page.screenshot({
|
||||
path: './e2e/playwright/temp1.png',
|
||||
fullPage: true,
|
||||
})
|
||||
await fn()
|
||||
const isImageDiff = async () => {
|
||||
await page.screenshot({
|
||||
path: './e2e/playwright/temp2.png',
|
||||
fullPage: true,
|
||||
})
|
||||
const screenshot1 = PNG.sync.read(
|
||||
await fsp.readFile('./e2e/playwright/temp1.png')
|
||||
)
|
||||
const screenshot2 = PNG.sync.read(
|
||||
await fsp.readFile('./e2e/playwright/temp2.png')
|
||||
)
|
||||
const actualDiffCount = pixelMatch(
|
||||
screenshot1.data,
|
||||
screenshot2.data,
|
||||
null,
|
||||
screenshot1.width,
|
||||
screenshot2.height
|
||||
)
|
||||
return actualDiffCount > diffCount
|
||||
}
|
||||
|
||||
// run isImageDiff every 50ms until it returns true or 5 seconds have passed (100 times)
|
||||
let count = 0
|
||||
const interval = setInterval(async () => {
|
||||
count++
|
||||
if (await isImageDiff()) {
|
||||
clearInterval(interval)
|
||||
resolve(true)
|
||||
} else if (count > 100) {
|
||||
clearInterval(interval)
|
||||
resolve(false)
|
||||
}
|
||||
}, 50)
|
||||
}),
|
||||
}
|
||||
}
|
105
e2e/tauri/specs/auth.e2e.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { browser, $, expect } from '@wdio/globals'
|
||||
import fs from 'fs/promises'
|
||||
|
||||
const defaultDir = `${process.env.HOME}/Documents/zoo-modeling-app-projects`
|
||||
const userCodeDir = '/tmp/kittycad_user_code'
|
||||
|
||||
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 fs.rm(defaultDir, { force: true, recursive: true })
|
||||
await fs.rm(userCodeDir, { force: true })
|
||||
|
||||
const signInButton = await $('[data-testid="sign-in-button"]')
|
||||
expect(await signInButton.getText()).toEqual('Sign in')
|
||||
|
||||
await click(signInButton)
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
|
||||
// Get from main.rs
|
||||
const userCode = await (
|
||||
await fs.readFile('/tmp/kittycad_user_code')
|
||||
).toString()
|
||||
console.log(`Found user code ${userCode}`)
|
||||
|
||||
// Device flow: verify
|
||||
const token = process.env.KITTYCAD_API_TOKEN
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
const apiBaseUrl = process.env.VITE_KC_API_BASE_URL
|
||||
const verifyUrl = `${apiBaseUrl}/oauth2/device/verify?user_code=${userCode}`
|
||||
console.log(`GET ${verifyUrl}`)
|
||||
const vr = await fetch(verifyUrl, { headers })
|
||||
console.log(vr.status)
|
||||
|
||||
// Device flow: confirm
|
||||
const confirmUrl = `${apiBaseUrl}/oauth2/device/confirm`
|
||||
const data = JSON.stringify({ user_code: userCode })
|
||||
console.log(`POST ${confirmUrl} ${data}`)
|
||||
const cr = await fetch(confirmUrl, {
|
||||
headers,
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
console.log(cr.status)
|
||||
|
||||
// Now should be signed in
|
||||
const newFileButton = await $('[data-testid="home-new-file"]')
|
||||
expect(await newFileButton.getText()).toEqual('New file')
|
||||
})
|
||||
|
||||
it('opens the settings page, checks filesystem settings, and closes the settings page', async () => {
|
||||
const menuButton = await $('[data-testid="user-sidebar-toggle"]')
|
||||
await 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"]')
|
||||
await click(signoutButton)
|
||||
const newSignInButton = await $('[data-testid="sign-in-button"]')
|
||||
expect(await newSignInButton.getText()).toEqual('Sign in')
|
||||
})
|
||||
})
|
@ -1,11 +0,0 @@
|
||||
describe('Modeling App', () => {
|
||||
it('open the sign in page', async () => {
|
||||
const button = await $('#signin')
|
||||
expect(button).toHaveText('Sign in')
|
||||
|
||||
// Workaround for .click(), see https://github.com/tauri-apps/tauri/issues/6541
|
||||
await button.waitForClickable()
|
||||
await browser.execute('arguments[0].click();', button)
|
||||
// TODO: handle auth
|
||||
})
|
||||
})
|
11
index.html
@ -7,12 +7,17 @@
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="An open-source CAD modeling tool from the future by KittyCAD."
|
||||
content="An open-source CAD modeling tool from the future by Zoo."
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<script defer data-domain="app.kittycad.io" src="https://plausible.corp.kittycad.io/js/script.js"></script>
|
||||
<title>KittyCAD Modeling App</title>
|
||||
<link rel="stylesheet" href="https://use.typekit.net/zzv8rvm.css" />
|
||||
<script
|
||||
defer
|
||||
data-domain="app.zoo.dev"
|
||||
src="https://plausible.corp.zoo.dev/js/script.js"
|
||||
></script>
|
||||
<title>Zoo Modeling App</title>
|
||||
</head>
|
||||
<body class="body-bg">
|
||||
<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 ""
|
69
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.11.3",
|
||||
"version": "0.15.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.10.2",
|
||||
@ -8,10 +8,10 @@
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.13",
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@kittycad/lib": "^0.0.45",
|
||||
"@lezer/javascript": "^1.4.7",
|
||||
"@kittycad/lib": "^0.0.54",
|
||||
"@lezer/javascript": "^1.4.9",
|
||||
"@open-rpc/client-js": "^1.8.1",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@replit/codemirror-interact": "^6.3.0",
|
||||
@ -21,8 +21,9 @@
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.5.1",
|
||||
"@ts-stack/markdown": "^1.5.0",
|
||||
"@tweenjs/tween.js": "^23.1.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react": "^18.2.41",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@uiw/react-codemirror": "^4.21.20",
|
||||
"@xstate/inspect": "^0.8.0",
|
||||
@ -30,9 +31,10 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"formik": "^2.4.3",
|
||||
"fuse.js": "^6.6.2",
|
||||
"fuse.js": "^7.0.0",
|
||||
"http-server": "^14.1.1",
|
||||
"json-rpc-2.0": "^1.6.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"re-resizable": "^6.9.11",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@ -45,11 +47,12 @@
|
||||
"sketch-helpers": "^0.0.4",
|
||||
"swr": "^2.2.2",
|
||||
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
|
||||
"three": "^0.160.0",
|
||||
"toml": "^3.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2",
|
||||
"uuid": "^9.0.1",
|
||||
"vitest": "^0.34.6",
|
||||
"vitest": "^1.3.1",
|
||||
"vscode-jsonrpc": "^8.1.0",
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"wasm-pack": "^0.12.1",
|
||||
@ -60,6 +63,8 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"start:prod": "vite preview --port=3000",
|
||||
"serve": "vite serve --port=3000",
|
||||
"build": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source \"$HOME/.cargo/env\" && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y && yarn build:wasm && vite build",
|
||||
"build:local": "vite build",
|
||||
"build:both": "vite build",
|
||||
@ -68,19 +73,20 @@
|
||||
"test": "vitest --mode development",
|
||||
"test:nowatch": "vitest run --mode development",
|
||||
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
|
||||
"test:cov": "vitest run --coverage --mode development",
|
||||
"test:e2e": "wdio run wdio.conf.js",
|
||||
"test:e2e:tauri": "E2E_TAURI_ENABLED=true TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' wdio run wdio.conf.ts",
|
||||
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||
"fmt": "prettier --write ./src",
|
||||
"fmt-check": "prettier --check ./src",
|
||||
"fmt": "prettier --write ./src && prettier --write ./e2e",
|
||||
"fmt-check": "prettier --check ./src && prettier --check ./e2e",
|
||||
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
|
||||
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
|
||||
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
||||
"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",
|
||||
"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": {
|
||||
"trailingComma": "es5",
|
||||
@ -102,34 +108,45 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.22.9",
|
||||
"@babel/preset-env": "^7.23.3",
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@tauri-apps/cli": "^1.5.6",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/debounce-promise": "^3.1.8",
|
||||
"@types/isomorphic-fetch": "^0.0.36",
|
||||
"@types/react-modal": "^3.16.0",
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@types/three": "^0.160.0",
|
||||
"@types/uuid": "^9.0.4",
|
||||
"@types/wait-on": "^5.3.4",
|
||||
"@types/wicg-file-system-access": "^2020.9.6",
|
||||
"@types/ws": "^8.5.5",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"@vitest/coverage-istanbul": "^0.34.1",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@wdio/cli": "^8.24.3",
|
||||
"@wdio/globals": "^8.24.3",
|
||||
"@wdio/local-runner": "^8.24.3",
|
||||
"@wdio/mocha-framework": "^8.24.3",
|
||||
"@wdio/spec-reporter": "^8.24.2",
|
||||
"@xstate/cli": "^0.5.17",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-css-modules": "^2.11.0",
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"happy-dom": "^10.8.0",
|
||||
"husky": "^8.0.3",
|
||||
"pixelmatch": "^5.3.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.8.0",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"vite": "^4.5.0",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"vite": "^5.1.3",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"yarn": "^1.22.19",
|
||||
"@wdio/cli": "^7.7.3",
|
||||
"@wdio/local-runner": "^7.7.3",
|
||||
"@wdio/mocha-framework": "^7.7.3",
|
||||
"@wdio/spec-reporter": "^7.7.3"
|
||||
"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",
|
||||
"yarn": "^1.22.19"
|
||||
}
|
||||
}
|
||||
|
81
playwright.config.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './e2e/playwright',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 3 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: 'http://localhost:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'Google Chrome',
|
||||
use: { ...devices['Desktop Chrome'], channel: 'chrome' }, // or 'chrome-beta'
|
||||
},
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'chromium', // compat issue with encoding atm, so we're using the branded 'Google Chrome' instead
|
||||
// use: { ...devices['Desktop Chrome'] },
|
||||
// },
|
||||
|
||||
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'yarn serve',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
})
|
@ -1,3 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 3H4.5H11H11.2071L11.3536 3.14645L15.8536 7.64646L16 7.7929V8.00001V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V8.50001H11H10.5V8.00001V4H5V16H9.79076C9.93763 16.3628 10.1362 16.6992 10.3773 17H4.5H4V16.5V3.5V3ZM11.5 4.70711L14.2929 7.50001H11.5V4.70711ZM13 12V14H11V15H13V17H14V15H16V14H14V12H13Z" fill="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 475 B |
@ -1,3 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 3.5H4H7H7.16667L7.3 3.6L9.16667 5H16H16.5V5.5V7.5V10.3773C16.1992 10.1362 15.8628 9.93763 15.5 9.79076V8H4.5V15.5H10.5351C10.7529 15.8764 11.0302 16.2141 11.3542 16.5H4H3.5V16V7.5V4V3.5ZM4.5 4.5V7H15.5V6H9H8.83333L8.7 5.9L6.83333 4.5H4.5ZM13.5 11V13H11.5V14H13.5V16H14.5V14H16.5V13H14.5V11H13.5Z" fill="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 469 B |
@ -1,3 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 3.5H4.5V16.5H15.5V8.00001M11 3.5L15.5 8.00001M11 3.5V8.00001H15.5" stroke="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 200 B |
26
public/announce_release.py
Normal file
@ -0,0 +1,26 @@
|
||||
import requests
|
||||
import os
|
||||
|
||||
webhook_url = os.getenv('DISCORD_WEBHOOK_URL')
|
||||
release_version = os.getenv('RELEASE_VERSION')
|
||||
release_body = os.getenv('RELEASE_BODY')
|
||||
|
||||
# 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
|
||||
{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("Failed to send the message to Discord.")
|
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).
|
||||
|
||||
### 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?
|
||||
|
||||
@ -18,11 +18,11 @@ Plenty of you have professional CAD experience, and may not understand why codin
|
||||
- Reproducibility
|
||||
- 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.
|
||||
|
||||
- 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).
|
||||
- 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:
|
||||
|
BIN
public/kcma-logomark-outlined.png
Normal file
After Width: | Height: | Size: 11 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "KCMA",
|
||||
"name": "KittyCAD Modeling App",
|
||||
"short_name": "ZMA",
|
||||
"name": "Zoo Modeling App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
@ -1,4 +1,4 @@
|
||||
## KittyCAD Modeling App Roadmap
|
||||
## Zoo Modeling App Roadmap
|
||||
|
||||
This document ties into our [GH Discussions Feature List](https://github.com/KittyCAD/modeling-app/discussions). Please upvote any features that you want to see next, or add ones that are not listed and we will review.
|
||||
|
||||
|
13
public/zma-logomark-dark.svg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/zma-logomark-outlined.png
Normal file
After Width: | Height: | Size: 11 KiB |
13
public/zma-logomark.svg
Normal file
After Width: | Height: | Size: 13 KiB |
7
public/zoo-logo.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg width="438" height="145" viewBox="0 0 438 145" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M88.2136 25.3021V3.12744H0.595269V34.3994H79.827L0.609484 120.312H0.595269V120.326L0.581055 120.34L0.595269 120.355V141.364H20.8936L41.3341 119.189V141.364H128.952V110.092H49.7349L128.952 24.1649V3.12744L108.64 3.15587L88.2136 25.3021Z" fill="white"/>
|
||||
<path d="M167.36 72.4372C167.36 49.7366 185.824 31.2719 208.525 31.2719C216.514 31.2719 223.976 33.5605 230.288 37.5121L251.78 14.3709C239.698 5.34466 224.73 0 208.525 0C168.582 0 136.088 32.4944 136.088 72.4372C136.088 90.5465 142.769 107.135 153.828 119.857L175.32 96.7156C170.316 89.9069 167.36 81.5061 167.36 72.4372Z" fill="white"/>
|
||||
<path d="M241.745 48.1442C246.734 54.9671 249.691 63.3679 249.691 72.4368C249.691 95.1232 231.226 113.588 208.525 113.588C200.537 113.588 193.088 111.299 186.777 107.348L165.271 130.503C177.353 139.515 192.321 144.86 208.525 144.86C248.468 144.86 280.963 112.365 280.963 72.4368C280.963 54.3133 274.282 37.7249 263.223 25.0029L241.745 48.1442Z" fill="white"/>
|
||||
<path d="M419.312 25.0029L397.834 48.1442C402.823 54.9671 405.779 63.3679 405.779 72.4368C405.779 95.1232 387.315 113.588 364.614 113.588C356.626 113.588 349.177 111.299 342.866 107.348L321.359 130.503C333.442 139.515 348.41 144.86 364.614 144.86C404.557 144.86 437.051 112.365 437.051 72.4368C437.051 54.3133 430.371 37.7249 419.312 25.0029Z" fill="white"/>
|
||||
<path d="M323.449 72.4372C323.449 49.7366 341.913 31.2719 364.614 31.2719C372.603 31.2719 380.065 33.5605 386.376 37.5121L407.869 14.3709C395.786 5.34466 380.819 0 364.614 0C324.671 0 292.177 32.4944 292.177 72.4372C292.177 90.5465 298.858 107.135 309.916 119.857L331.409 96.7156C326.405 89.9069 323.449 81.5061 323.449 72.4372Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
289
src-tauri/Cargo.lock
generated
@ -67,9 +67,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
|
||||
[[package]]
|
||||
name = "app"
|
||||
@ -95,7 +95,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -542,17 +542,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.26"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
|
||||
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -582,7 +582,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -593,7 +593,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -899,7 +899,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1242,9 +1242,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.20"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049"
|
||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@ -1252,7 +1252,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.0.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@ -1530,9 +1530,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3"
|
||||
checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc"
|
||||
dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
@ -1652,9 +1652,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "json-patch"
|
||||
version = "1.0.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658"
|
||||
checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -1664,9 +1664,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.2.41"
|
||||
version = "0.2.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "874914cd40bfd43674406683bb3f0924d41780698a4ade96f2e180a73678bdd1"
|
||||
checksum = "a086e1a1bbddb3b38959c0f0ce6de6b3a3b7566e38e0b7d5fb101e91911beed4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1732,9 +1732,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.148"
|
||||
version = "0.2.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@ -1913,12 +1913,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "minisign-verify"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
@ -1940,9 +1934,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.8"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
@ -2200,11 +2194,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.55"
|
||||
version = "0.10.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
|
||||
checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.4.0",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
@ -2221,7 +2215,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2232,9 +2226,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.90"
|
||||
version = "0.9.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
|
||||
checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@ -2374,7 +2368,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.7.5",
|
||||
"structmeta",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2406,9 +2400,17 @@ version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
||||
dependencies = [
|
||||
"phf_macros 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_macros 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2451,6 +2453,16 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.2",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.8.0"
|
||||
@ -2467,16 +2479,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.10.0"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
|
||||
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
|
||||
dependencies = [
|
||||
"phf_generator 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
"proc-macro-hack",
|
||||
"phf_generator 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2497,6 +2508,15 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phonenumber"
|
||||
version = "0.3.3+8.13.9"
|
||||
@ -2535,7 +2555,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2637,9 +2657,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.67"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -2655,9 +2675,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -3112,9 +3132,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.15"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
|
||||
checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"bytes",
|
||||
@ -3129,9 +3149,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.15"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
|
||||
checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3215,9 +3235,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.192"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -3233,13 +3253,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.192"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3255,9 +3275,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.108"
|
||||
version = "1.0.113"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
||||
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||
dependencies = [
|
||||
"itoa 1.0.6",
|
||||
"ryu",
|
||||
@ -3282,7 +3302,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3332,7 +3352,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3438,9 +3458,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.4"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
|
||||
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
@ -3536,7 +3556,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3547,7 +3567,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3585,9 +3605,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.33"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3740,12 +3760,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.5.2"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bfe673cf125ef364d6f56b15e8ce7537d9ca7e4dae1cf6fbbdeed2e024db3d9"
|
||||
checksum = "fd27c04b9543776a972c86ccf70660b517ecabbeced9fb58d8b961a13ad129af"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.2",
|
||||
"bytes",
|
||||
"cocoa",
|
||||
"dirs-next",
|
||||
@ -3759,7 +3778,6 @@ dependencies = [
|
||||
"heck 0.4.1",
|
||||
"http",
|
||||
"ignore",
|
||||
"minisign-verify",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"open",
|
||||
@ -3784,21 +3802,19 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "defbfc551bd38ab997e5f8e458f87396d2559d05ce32095076ad6c30f7fc5f9c"
|
||||
checksum = "e9914a4715e0b75d9f387a285c7e26b5bbfeb1249ad9f842675a82481565c532"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@ -3815,9 +3831,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "1.4.1"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b3475e55acec0b4a50fb96435f19631fb58cbcd31923e1a213de5c382536bbb"
|
||||
checksum = "a1554c5857f65dbc377cefb6b97c8ac77b1cb2a90d30d3448114d5d6b48a77fc"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"brotli",
|
||||
@ -3841,9 +3857,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "1.4.1"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613740228de92d9196b795ac455091d3a5fbdac2654abb8bb07d010b62ab43af"
|
||||
checksum = "277abf361a3a6993ec16bcbb179de0d6518009b851090a01adfea12ac89fa875"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
@ -3856,7 +3872,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs-extra"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#6865299149ffd183365e3ff291acf3edac78ca61"
|
||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#01211ff0759d578e0e9ac8c98c31fdf09077eb34"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@ -3867,9 +3883,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "0.14.1"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07f8e9e53e00e9f41212c115749e87d5cd2a9eebccafca77a19722eeecd56d43"
|
||||
checksum = "cf2d0652aa2891ff3e9caa2401405257ea29ab8372cce01f186a5825f1bd0e76"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@ -3888,9 +3904,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.14.1"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8141d72b6b65f2008911e9ef5b98a68d1e3413b7a1464e8f85eb3673bb19a895"
|
||||
checksum = "6cae61fbc731f690a4899681c9052dde6d05b159b44563ace8186fc1bfb7d158"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"gtk",
|
||||
@ -3908,9 +3924,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.5.0"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34d55e185904a84a419308d523c2c6891d5e2dbcee740c4997eb42e75a7b0f46"
|
||||
checksum = "ece74810b1d3d44f29f732a7ae09a63183d63949bbdd59c61f8ed2a1b70150db"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"ctor",
|
||||
@ -3923,7 +3939,7 @@ dependencies = [
|
||||
"kuchikiki",
|
||||
"log",
|
||||
"memchr",
|
||||
"phf 0.10.1",
|
||||
"phf 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"semver",
|
||||
@ -3933,7 +3949,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"url",
|
||||
"walkdir",
|
||||
"windows 0.39.0",
|
||||
"windows-version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3993,7 +4009,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4035,9 +4051,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.33.0"
|
||||
version = "1.36.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
|
||||
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@ -4045,7 +4061,7 @@ dependencies = [
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.4",
|
||||
"socket2 0.5.5",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -4177,7 +4193,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4427,7 +4443,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -4461,7 +4477,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -4757,12 +4773,36 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-tokens"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597"
|
||||
|
||||
[[package]]
|
||||
name = "windows-version"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75aa004c988e080ad34aff5739c39d0312f4684699d6d71fc8a198d057b8b9b4"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@ -4775,6 +4815,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.37.0"
|
||||
@ -4799,6 +4845,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.37.0"
|
||||
@ -4823,6 +4875,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.37.0"
|
||||
@ -4847,6 +4905,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.37.0"
|
||||
@ -4871,6 +4935,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@ -4883,6 +4953,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.37.0"
|
||||
@ -4907,6 +4983,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.15"
|
||||
@ -4938,9 +5020,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.24.4"
|
||||
version = "0.24.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ef04bdad49eba2e01f06e53688c8413bd6a87b0bc14b72284465cf96e3578e"
|
||||
checksum = "64a70547e8f9d85da0f5af609143f7bde3ac7457a6e1073104d9b73d6c5ac744"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"block",
|
||||
@ -5012,14 +5094,3 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
@ -12,17 +12,17 @@ rust-version = "1.60"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.5.0", features = [] }
|
||||
tauri-build = { version = "1.5.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
kittycad = "0.2.41"
|
||||
kittycad = "0.2.53"
|
||||
oauth2 = "4.4.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "1.5.2", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
|
||||
tauri = { version = "1.5.4", 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" }
|
||||
tokio = { version = "1.33.0", features = ["time"] }
|
||||
tokio = { version = "1.36.0", features = ["time"] }
|
||||
toml = "0.8.2"
|
||||
|
||||
[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 |
@ -1,10 +1,13 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
|
||||
use anyhow::Result;
|
||||
use oauth2::TokenResponse;
|
||||
use std::process::Command;
|
||||
use tauri::{InvokeError, Manager};
|
||||
const DEFAULT_HOST: &str = "https://api.kittycad.io";
|
||||
|
||||
@ -70,8 +73,21 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
|
||||
// Open the system browser with the auth_uri.
|
||||
// We do this in the browser and not a separate window because we want 1password and
|
||||
// other crap to work well.
|
||||
tauri::api::shell::open(&app.shell_scope(), auth_uri.secret(), None)
|
||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||
// TODO: find a better way to share this value with tauri e2e tests
|
||||
// Here we're using an env var to enable the /tmp file (windows not supported for now)
|
||||
// and bypass the shell::open call as it fails on GitHub Actions.
|
||||
let e2e_tauri_enabled = env::var("E2E_TAURI_ENABLED").is_ok();
|
||||
if e2e_tauri_enabled {
|
||||
println!(
|
||||
"E2E_TAURI_ENABLED is set, won't open {} externally",
|
||||
auth_uri.secret()
|
||||
);
|
||||
fs::write("/tmp/kittycad_user_code", details.user_code().secret())
|
||||
.expect("Unable to write /tmp/kittycad_user_code file");
|
||||
} else {
|
||||
tauri::api::shell::open(&app.shell_scope(), auth_uri.secret(), None)
|
||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||
}
|
||||
|
||||
// Wait for the user to login.
|
||||
let token = auth_client
|
||||
|
@ -1,14 +1,13 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"build": {
|
||||
"beforeBuildCommand": "yarn build:both",
|
||||
"beforeDevCommand": "yarn start",
|
||||
"devPath": "http://localhost:3000",
|
||||
"distDir": "../build"
|
||||
},
|
||||
"package": {
|
||||
"productName": "kittycad-modeling",
|
||||
"version": "0.11.3"
|
||||
"productName": "zoo-modeling-app",
|
||||
"version": "0.15.3"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
@ -85,7 +84,7 @@
|
||||
"fullscreen": false,
|
||||
"height": 1200,
|
||||
"resizable": true,
|
||||
"title": "KittyCAD Modeling",
|
||||
"title": "Zoo Modeling App",
|
||||
"width": 1800
|
||||
}
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"package": {
|
||||
"productName": "KittyCAD Modeling"
|
||||
"productName": "Zoo Modeling App"
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"updater": {
|
||||
"active": true,
|
||||
"endpoints": [
|
||||
"https://dl.kittycad.io/releases/modeling-app/last_update.json"
|
||||
"https://dl.zoo.dev/releases/modeling-app/last_update.json"
|
||||
],
|
||||
"dialog": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUzNzA4MjBEQjFBRTY4NzYKUldSMmFLNnhEWUp3NCtsT21Jd05wQktOaGVkOVp6MUFma0hNTDRDSnI2RkJJTEZOWG1ncFhqcU8K"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"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'
|
||||
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} />
|
||||
}
|
103
src/App.tsx
@ -19,21 +19,24 @@ import {
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { getNormalisedCoordinates } from './lib/utils'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { IndexLoaderData } from './Router'
|
||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { paths } from 'lib/paths'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { onboardingPaths } from 'routes/Onboarding'
|
||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { CodeMenu } from 'components/CodeMenu'
|
||||
import { TextEditor } from 'components/TextEditor'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||
import { engineCommandManager } from './lang/std/engineConnection'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
|
||||
export function App() {
|
||||
const { project, file } = useLoaderData() as IndexLoaderData
|
||||
const navigate = useNavigate()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
|
||||
useHotKeyListener()
|
||||
const {
|
||||
@ -51,8 +54,7 @@ export function App() {
|
||||
}))
|
||||
|
||||
const { settings } = useGlobalStateContext()
|
||||
const { showDebugPanel, onboardingStatus, cameraControls, theme } =
|
||||
settings?.context || {}
|
||||
const { showDebugPanel, onboardingStatus, theme } = settings?.context || {}
|
||||
const { state, send } = useModelingContext()
|
||||
|
||||
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
|
||||
@ -71,6 +73,16 @@ export function App() {
|
||||
useHotkeys('shift + e', () => togglePane('kclErrors'))
|
||||
useHotkeys('shift + d', () => togglePane('debug'))
|
||||
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(
|
||||
(p) => p === onboardingStatus
|
||||
@ -84,9 +96,11 @@ export function App() {
|
||||
|
||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||
engineCommandManager.sendSceneCommand(message)
|
||||
}, 16)
|
||||
}, 1000 / 15)
|
||||
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
e.nativeEvent.preventDefault()
|
||||
if (state.matches('Sketch')) {
|
||||
return
|
||||
}
|
||||
|
||||
const { x, y } = getNormalisedCoordinates({
|
||||
clientX: e.clientX,
|
||||
@ -97,58 +111,11 @@ export function App() {
|
||||
|
||||
const newCmdId = uuidv4()
|
||||
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({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'camera_drag_move',
|
||||
interaction,
|
||||
window: { x, y },
|
||||
type: 'highlight_set_entity',
|
||||
selected_at_window: { x, y },
|
||||
},
|
||||
cmd_id: newCmdId,
|
||||
})
|
||||
@ -172,11 +139,8 @@ export function App() {
|
||||
<ModalContainer />
|
||||
<Resizable
|
||||
className={
|
||||
'h-full flex flex-col flex-1 z-10 my-5 ml-5 pr-1 transition-opacity transition-duration-75 ' +
|
||||
(buttonDownInStream || onboardingStatus === 'camera'
|
||||
? ' pointer-events-none '
|
||||
: ' ') +
|
||||
paneOpacity
|
||||
'pointer-events-none h-full flex flex-col flex-1 z-10 my-5 ml-5 pr-1 transition-opacity transition-duration-75 ' +
|
||||
+paneOpacity
|
||||
}
|
||||
defaultSize={{
|
||||
width: '550px',
|
||||
@ -188,10 +152,16 @@ export function App() {
|
||||
maxHeight={'auto'}
|
||||
handleClasses={{
|
||||
right:
|
||||
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
|
||||
'hover:bg-chalkboard-10/50 bg-transparent transition-colors duration-75 transition-ease-out delay-100 ' +
|
||||
(buttonDownInStream || onboardingStatus === 'camera'
|
||||
? 'pointer-events-none '
|
||||
: 'pointer-events-auto'),
|
||||
}}
|
||||
>
|
||||
<div id="code-pane" className="h-full flex flex-col justify-between">
|
||||
<div
|
||||
id="code-pane"
|
||||
className="h-full flex flex-col justify-between pointer-events-none"
|
||||
>
|
||||
<CollapsiblePanel
|
||||
title="Code"
|
||||
icon={faCode}
|
||||
@ -226,7 +196,7 @@ export function App() {
|
||||
<Stream className="absolute inset-0 z-0" />
|
||||
{showDebugPanel && (
|
||||
<DebugPanel
|
||||
title="Debug (AST Explorer)"
|
||||
title="Debug"
|
||||
className={
|
||||
'transition-opacity transition-duration-75 ' +
|
||||
paneOpacity +
|
||||
@ -235,6 +205,7 @@ export function App() {
|
||||
open={openPanes.includes('debug')}
|
||||
/>
|
||||
)}
|
||||
{/* <CamToggle /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
||||
|
||||
return isLoggingIn ? (
|
||||
<Loading>Loading KittyCAD Modeling App...</Loading>
|
||||
<Loading>
|
||||
<span data-testid="initial-load">Loading Modeling App...</span>
|
||||
</Loading>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)
|
||||
|
@ -14,10 +14,7 @@ import {
|
||||
import { useEffect } from 'react'
|
||||
import { ErrorPage } from './components/ErrorPage'
|
||||
import { Settings } from './routes/Settings'
|
||||
import Onboarding, {
|
||||
onboardingRoutes,
|
||||
onboardingPaths,
|
||||
} from './routes/Onboarding'
|
||||
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
|
||||
import SignIn from './routes/SignIn'
|
||||
import { Auth } from './Auth'
|
||||
import { isTauri } from './lib/isTauri'
|
||||
@ -29,7 +26,7 @@ import {
|
||||
isProjectDirectory,
|
||||
PROJECT_ENTRYPOINT,
|
||||
} 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 { WasmErrBanner } from './components/WasmErrBanner'
|
||||
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
||||
@ -38,13 +35,18 @@ import {
|
||||
settingsMachine,
|
||||
} from './machines/settingsMachine'
|
||||
import { ContextFrom } from 'xstate'
|
||||
import CommandBarProvider from 'components/CommandBar'
|
||||
import CommandBarProvider, {
|
||||
CommandBar,
|
||||
} from 'components/CommandBar/CommandBar'
|
||||
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
||||
import { KclContextProvider, kclManager } from 'lang/KclSinglton'
|
||||
import { KclContextProvider, kclManager } from 'lang/KclSingleton'
|
||||
import FileMachineProvider from 'components/FileMachineProvider'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
import { paths } from 'lib/paths'
|
||||
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
|
||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||
|
||||
if (VITE_KC_SENTRY_DSN && !TEST) {
|
||||
Sentry.init({
|
||||
@ -78,42 +80,8 @@ if (VITE_KC_SENTRY_DSN && !TEST) {
|
||||
})
|
||||
}
|
||||
|
||||
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 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]
|
||||
|
||||
const addGlobalContextToElements = (
|
||||
@ -145,18 +113,19 @@ const router = createBrowserRouter(
|
||||
{
|
||||
path: paths.FILE + '/:id',
|
||||
element: (
|
||||
<Auth>
|
||||
<FileMachineProvider>
|
||||
<KclContextProvider>
|
||||
<KclContextProvider>
|
||||
<Auth>
|
||||
<FileMachineProvider>
|
||||
<ModelingMachineProvider>
|
||||
<Outlet />
|
||||
<App />
|
||||
<CommandBar />
|
||||
</ModelingMachineProvider>
|
||||
<WasmErrBanner />
|
||||
</KclContextProvider>
|
||||
</FileMachineProvider>
|
||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||
</Auth>
|
||||
</FileMachineProvider>
|
||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||
</Auth>
|
||||
</KclContextProvider>
|
||||
),
|
||||
id: paths.FILE,
|
||||
loader: async ({
|
||||
@ -209,6 +178,10 @@ const router = createBrowserRouter(
|
||||
const children = await readDir(projectPath, { recursive: true })
|
||||
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 {
|
||||
code,
|
||||
project: {
|
||||
@ -246,9 +219,10 @@ const router = createBrowserRouter(
|
||||
<Auth>
|
||||
<Outlet />
|
||||
<Home />
|
||||
<CommandBar />
|
||||
</Auth>
|
||||
),
|
||||
loader: async () => {
|
||||
loader: async (): Promise<HomeLoaderData | Response> => {
|
||||
if (!isTauri()) {
|
||||
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
|
||||
}
|
||||
@ -259,6 +233,7 @@ const router = createBrowserRouter(
|
||||
const projectDir = await initializeProjectDirectory(
|
||||
persistedSettings.defaultDirectory || ''
|
||||
)
|
||||
let newDefaultDirectory: string | undefined = undefined
|
||||
if (projectDir !== persistedSettings.defaultDirectory) {
|
||||
localStorage.setItem(
|
||||
SETTINGS_PERSIST_KEY,
|
||||
@ -267,6 +242,7 @@ const router = createBrowserRouter(
|
||||
defaultDirectory: projectDir,
|
||||
})
|
||||
)
|
||||
newDefaultDirectory = projectDir
|
||||
}
|
||||
const projectsNoMeta = (await readDir(projectDir)).filter(
|
||||
isProjectDirectory
|
||||
@ -282,6 +258,7 @@ const router = createBrowserRouter(
|
||||
|
||||
return {
|
||||
projects,
|
||||
newDefaultDirectory,
|
||||
}
|
||||
},
|
||||
children: [
|
||||
|
@ -1,106 +0,0 @@
|
||||
.toolbarWrapper {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
@apply flex gap-4 items-center rounded-full;
|
||||
@apply border border-cool-20/30 bg-cool-10/50;
|
||||
}
|
||||
|
||||
:global(.dark) .toolbar {
|
||||
@apply border-cool-100/50 bg-cool-120/50;
|
||||
}
|
||||
|
||||
:global(.sketch) .toolbar {
|
||||
@apply border-fern-20/20 bg-fern-10/20;
|
||||
}
|
||||
|
||||
:global(.dark .sketch) .toolbar {
|
||||
@apply border-fern-120/50 bg-fern-100/30;
|
||||
}
|
||||
|
||||
.toolbarCap {
|
||||
@apply text-sm font-bold;
|
||||
@apply bg-cool-20/50 text-cool-100;
|
||||
}
|
||||
|
||||
:global(.dark) .toolbarCap {
|
||||
@apply bg-cool-90/50 text-cool-30;
|
||||
}
|
||||
|
||||
:global(.sketch) .toolbarCap {
|
||||
@apply bg-fern-20/50 text-fern-100;
|
||||
}
|
||||
|
||||
:global(.dark .sketch) .toolbarCap {
|
||||
@apply bg-fern-90/50 text-fern-30;
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply self-stretch flex items-center px-4 py-1;
|
||||
@apply rounded-l-full;
|
||||
}
|
||||
|
||||
.popoverToggle {
|
||||
@apply self-stretch m-0 flex items-center px-4 py-1;
|
||||
@apply rounded-r-full border-none;
|
||||
@apply hover:bg-cool-20;
|
||||
}
|
||||
|
||||
.toolbarButtons::-webkit-scrollbar {
|
||||
@apply h-0.5;
|
||||
}
|
||||
|
||||
.toolbarButtons {
|
||||
@apply flex items-center overflow-x-auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.toolbarButtons button {
|
||||
@apply text-chalkboard-90 bg-chalkboard-10/50 border-chalkboard-50 whitespace-nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@apply gap-1.5 p-0.5 pr-1;
|
||||
@apply rounded-sm;
|
||||
}
|
||||
:global(.dark) .toolbarButtons button {
|
||||
@apply text-chalkboard-30 bg-chalkboard-90/50 border-chalkboard-50;
|
||||
}
|
||||
.toolbarButtons button:hover {
|
||||
@apply text-cool-90 bg-cool-10;
|
||||
}
|
||||
:global(.sketch) .toolbarButtons button:hover {
|
||||
@apply text-fern-90 bg-fern-10;
|
||||
}
|
||||
.toolbarButtons button:disabled {
|
||||
@apply text-chalkboard-70 bg-chalkboard-30;
|
||||
}
|
||||
.toolbarButtons button:disabled:hover {
|
||||
@apply !bg-inherit !text-inherit cursor-not-allowed;
|
||||
}
|
||||
|
||||
:global(.dark) .toolbarButtons button {
|
||||
@apply text-chalkboard-20 border-chalkboard-50;
|
||||
}
|
||||
:global(.dark) .toolbarButtons button:hover {
|
||||
@apply text-cool-10 border-chalkboard-50 bg-cool-90;
|
||||
}
|
||||
:global(.dark .sketch) .toolbarButtons button:hover {
|
||||
@apply text-fern-10 border-chalkboard-50 bg-fern-90;
|
||||
}
|
||||
:global(.dark) .toolbarButtons button:disabled {
|
||||
@apply text-chalkboard-40 bg-chalkboard-80;
|
||||
}
|
||||
|
||||
:global(.dark) .popoverToggle {
|
||||
@apply hover:bg-cool-90;
|
||||
}
|
||||
|
||||
:global(.sketch) .popoverToggle {
|
||||
@apply hover:bg-fern-20;
|
||||
}
|
||||
|
||||
:global(.dark .sketch) .popoverToggle {
|
||||
@apply hover:bg-fern-90;
|
||||
}
|
366
src/Toolbar.tsx
@ -1,30 +1,41 @@
|
||||
import { Fragment, WheelEvent, useRef, useMemo } from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import styles from './Toolbar.module.css'
|
||||
import { WheelEvent, useRef, useMemo } from 'react'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { ActionIcon } from 'components/ActionIcon'
|
||||
import { engineCommandManager } from './lang/std/engineConnection'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export const sketchButtonClassnames = {
|
||||
background:
|
||||
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-fern-20 dark:group-hover:bg-fern-10 dark:hover:bg-fern-10 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50',
|
||||
icon: 'text-fern-20 h-auto group-hover:text-fern-10 hover:text-fern-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-60 hover:group-disabled:text-inherit',
|
||||
}
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||
import {
|
||||
NetworkHealthState,
|
||||
useNetworkStatus,
|
||||
} from 'components/NetworkHealthIndicator'
|
||||
import { useStore } from 'useStore'
|
||||
|
||||
export const Toolbar = () => {
|
||||
const platform = usePlatform()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const toolbarButtonsRef = useRef<HTMLSpanElement>(null)
|
||||
const pathId = useMemo(
|
||||
() =>
|
||||
isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactMap,
|
||||
context.selectionRanges
|
||||
),
|
||||
[engineCommandManager.artifactMap, context.selectionRanges]
|
||||
)
|
||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||
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'
|
||||
const pathId = useMemo(() => {
|
||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
||||
return false
|
||||
}
|
||||
return isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactMap,
|
||||
context.selectionRanges
|
||||
)
|
||||
}, [engineCommandManager.artifactMap, context.selectionRanges])
|
||||
const { overallState } = useNetworkStatus()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { isStreamReady } = useStore((s) => ({
|
||||
isStreamReady: s.isStreamReady,
|
||||
}))
|
||||
const disableAllButtons =
|
||||
overallState !== NetworkHealthState.Ok || isExecuting || !isStreamReady
|
||||
|
||||
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
||||
const span = toolbarButtonsRef.current
|
||||
@ -35,72 +46,113 @@ export const Toolbar = () => {
|
||||
span.scrollLeft = span.scrollLeft += ev.deltaY
|
||||
}
|
||||
|
||||
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||
function ToolbarButtons({
|
||||
className = '',
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLElement>) {
|
||||
return (
|
||||
<span
|
||||
<ul
|
||||
{...props}
|
||||
ref={toolbarButtonsRef}
|
||||
onWheel={handleToolbarButtonsWheelEvent}
|
||||
className={styles.toolbarButtons + ' ' + className}
|
||||
className={
|
||||
'm-0 py-1 rounded-l-sm flex gap-2 items-center overflow-x-auto ' +
|
||||
className
|
||||
}
|
||||
style={{ scrollbarWidth: 'thin' }}
|
||||
>
|
||||
{state.nextEvents.includes('Enter sketch') && (
|
||||
<button
|
||||
onClick={() => send({ type: 'Enter sketch' })}
|
||||
className="group"
|
||||
>
|
||||
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||
Start Sketch
|
||||
</button>
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
||||
}
|
||||
icon={{
|
||||
icon: 'sketch',
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
>
|
||||
<span data-testid="start-sketch">Start Sketch</span>
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
{state.nextEvents.includes('Enter sketch') && pathId && (
|
||||
<button
|
||||
onClick={() => send({ type: 'Enter sketch' })}
|
||||
className="group"
|
||||
>
|
||||
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||
Edit Sketch
|
||||
</button>
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => send({ type: 'Enter sketch' })}
|
||||
icon={{
|
||||
icon: 'sketch',
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
>
|
||||
Edit Sketch
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
{state.nextEvents.includes('Cancel') && !state.matches('idle') && (
|
||||
<button onClick={() => send({ type: 'Cancel' })} className="group">
|
||||
<ActionIcon icon="exit" className="!p-0.5" size="md" />
|
||||
Exit Sketch
|
||||
</button>
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => send({ type: 'Cancel' })}
|
||||
icon={{
|
||||
icon: 'arrowLeft',
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
>
|
||||
Exit Sketch
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
{state.matches('Sketch') && !state.matches('idle') && (
|
||||
<button
|
||||
onClick={() =>
|
||||
state.matches('Sketch.Line Tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip tool')
|
||||
}
|
||||
className={
|
||||
'group ' +
|
||||
(state.matches('Sketch.Line Tool')
|
||||
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
|
||||
: '')
|
||||
}
|
||||
>
|
||||
<ActionIcon icon="line" className="!p-0.5" size="md" />
|
||||
Line
|
||||
</button>
|
||||
)}
|
||||
{state.matches('Sketch') && (
|
||||
<button
|
||||
onClick={() =>
|
||||
state.matches('Sketch.Move Tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip move tool')
|
||||
}
|
||||
className={
|
||||
'group ' +
|
||||
(state.matches('Sketch.Move Tool')
|
||||
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
|
||||
: '')
|
||||
}
|
||||
>
|
||||
<ActionIcon icon="move" className="!p-0.5" size="md" />
|
||||
Move
|
||||
</button>
|
||||
<>
|
||||
<li className="contents" key="line-button">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
state?.matches('Sketch.Line 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"
|
||||
icon={{
|
||||
icon: 'line',
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
>
|
||||
Line
|
||||
</ActionButton>
|
||||
</li>
|
||||
<li className="contents" key="tangential-arc-button">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
state.matches('Sketch.Tangential arc to')
|
||||
? send('CancelSketch')
|
||||
: send('Equip tangential arc to')
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
||||
icon={{
|
||||
icon: 'arc',
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={
|
||||
(!state.can('Equip tangential arc to') &&
|
||||
!state.matches('Sketch.Tangential arc to')) ||
|
||||
disableAllButtons
|
||||
}
|
||||
>
|
||||
Tangential Arc
|
||||
</ActionButton>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
{state.matches('Sketch.SketchIdle') &&
|
||||
state.nextEvents
|
||||
@ -109,103 +161,87 @@ export const Toolbar = () => {
|
||||
eventName.includes('Make segment') ||
|
||||
eventName.includes('Constrain')
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const aisEnabled = state.nextEvents
|
||||
.filter((event) => state.can(event as any))
|
||||
.includes(a)
|
||||
const bIsEnabled = state.nextEvents
|
||||
.filter((event) => state.can(event as any))
|
||||
.includes(b)
|
||||
if (aisEnabled && !bIsEnabled) {
|
||||
return -1
|
||||
}
|
||||
if (!aisEnabled && bIsEnabled) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
.map((eventName) => (
|
||||
<button
|
||||
key={eventName}
|
||||
onClick={() => send(eventName)}
|
||||
className="group"
|
||||
disabled={
|
||||
!state.nextEvents
|
||||
.filter((event) => state.can(event as any))
|
||||
.includes(eventName)
|
||||
}
|
||||
title={eventName}
|
||||
>
|
||||
<ActionIcon
|
||||
icon={'line'} // TODO
|
||||
bgClassName={sketchButtonClassnames.background}
|
||||
iconClassName={sketchButtonClassnames.icon}
|
||||
size="md"
|
||||
/>
|
||||
{eventName
|
||||
.replace('Make segment ', '')
|
||||
.replace('Constrain ', '')}
|
||||
</button>
|
||||
<li className="contents" key={eventName}>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
className="text-sm"
|
||||
key={eventName}
|
||||
onClick={() => send(eventName)}
|
||||
disabled={
|
||||
!state.nextEvents
|
||||
.filter((event) => state.can(event as any))
|
||||
.includes(eventName) || disableAllButtons
|
||||
}
|
||||
title={eventName}
|
||||
icon={{
|
||||
icon: 'line',
|
||||
bgClassName,
|
||||
}}
|
||||
>
|
||||
{eventName
|
||||
.replace('Make segment ', '')
|
||||
.replace('Constrain ', '')}
|
||||
</ActionButton>
|
||||
</li>
|
||||
))}
|
||||
{state.matches('idle') && (
|
||||
<button
|
||||
onClick={() => send('extrude intent')}
|
||||
disabled={!state.can('extrude intent')}
|
||||
className="group"
|
||||
title={
|
||||
state.can('extrude intent')
|
||||
? 'extrude'
|
||||
: 'sketches need to be closed, or not already extruded'
|
||||
}
|
||||
>
|
||||
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
||||
Extrude
|
||||
</button>
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
className="text-sm"
|
||||
onClick={() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Extrude', ownerMachine: 'modeling' },
|
||||
})
|
||||
}
|
||||
disabled={!state.can('Extrude') || disableAllButtons}
|
||||
title={
|
||||
state.can('Extrude')
|
||||
? 'extrude'
|
||||
: 'sketches need to be closed, or not already extruded'
|
||||
}
|
||||
icon={{
|
||||
icon: 'extrude',
|
||||
bgClassName,
|
||||
}}
|
||||
>
|
||||
Extrude
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
</span>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
className={
|
||||
styles.toolbarWrapper + state.matches('Sketch') ? ' sketch' : ''
|
||||
}
|
||||
>
|
||||
<div className={styles.toolbar}>
|
||||
<span className={styles.toolbarCap + ' ' + styles.label}>
|
||||
{state.matches('Sketch') ? '2D' : '3D'}
|
||||
</span>
|
||||
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
||||
<ToolbarButtons />
|
||||
</menu>
|
||||
<Popover.Button
|
||||
className={styles.toolbarCap + ' ' + styles.popoverToggle}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSearch} />
|
||||
</Popover.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
<div className="max-w-full flex items-stretch rounded-l-sm rounded-r-full bg-chalkboard-10 dark:bg-chalkboard-100 relative">
|
||||
<menu className="flex-1 pl-1 pr-2 py-0 overflow-hidden rounded-l-sm whitespace-nowrap bg-chalkboard-10 dark:bg-chalkboard-100 border-solid border border-energy-10 dark:border-chalkboard-90 border-r-0">
|
||||
<ToolbarButtons />
|
||||
</menu>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => commandBarSend({ type: 'Open' })}
|
||||
className="rounded-r-full pr-4 self-stretch border-energy-10 hover:border-energy-10 dark:border-chalkboard-80 bg-energy-10/50 hover:bg-energy-10 dark:bg-chalkboard-80 dark:text-energy-10"
|
||||
>
|
||||
<Popover.Overlay className="fixed inset-0 bg-chalkboard-110/20 dark:bg-chalkboard-110/50" />
|
||||
</Transition>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="opacity-0 translate-y-1 scale-95"
|
||||
enterTo="opacity-100 translate-y-0 scale-100"
|
||||
leave="transition ease-out duration-75"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-2"
|
||||
>
|
||||
<Popover.Panel className="absolute top-0 w-screen max-w-xl left-1/2 -translate-x-1/2 flex flex-col gap-8 bg-chalkboard-10 dark:bg-chalkboard-100 p-5 rounded border border-chalkboard-20/30 dark:border-chalkboard-70/50">
|
||||
<section className="flex justify-between items-center">
|
||||
<p
|
||||
className={`${styles.toolbarCap} ${styles.label} !self-center rounded-r-full w-fit`}
|
||||
>
|
||||
You're in {state.matches('Sketch') ? '2D' : '3D'}
|
||||
</p>
|
||||
<Popover.Button className="p-2 flex items-center justify-center rounded-sm bg-chalkboard-20 text-chalkboard-110 dark:bg-chalkboard-70 dark:text-chalkboard-20 border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-60">
|
||||
<FontAwesomeIcon icon={faX} className="w-4 h-4" />
|
||||
</Popover.Button>
|
||||
</section>
|
||||
<section>
|
||||
<ToolbarButtons className="flex-wrap" />
|
||||
</section>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
{platform === 'darwin' ? '⌘K' : 'Ctrl+/'}
|
||||
</ActionButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
879
src/clientSideScene/CameraControls.ts
Normal file
@ -0,0 +1,879 @@
|
||||
import { MouseGuard } from 'lib/cameraControls'
|
||||
import {
|
||||
Euler,
|
||||
MathUtils,
|
||||
Matrix4,
|
||||
OrthographicCamera,
|
||||
PerspectiveCamera,
|
||||
Quaternion,
|
||||
Spherical,
|
||||
Vector2,
|
||||
Vector3,
|
||||
} from 'three'
|
||||
import {
|
||||
DEBUG_SHOW_INTERSECTION_PLANE,
|
||||
INTERSECTION_PLANE_LAYER,
|
||||
SKETCH_LAYER,
|
||||
ZOOM_MAGIC_NUMBER,
|
||||
} from './sceneInfra'
|
||||
import { EngineCommand, engineCommandManager } from 'lang/std/engineConnection'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { deg2Rad } from 'lib/utils2d'
|
||||
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
||||
import * as TWEEN from '@tweenjs/tween.js'
|
||||
import { isQuaternionVertical } from './helpers'
|
||||
|
||||
const ORTHOGRAPHIC_CAMERA_SIZE = 20
|
||||
const FRAMES_TO_ANIMATE_IN = 30
|
||||
|
||||
const tempQuaternion = new Quaternion() // just used for maths
|
||||
|
||||
interface ThreeCamValues {
|
||||
position: Vector3
|
||||
quaternion: Quaternion
|
||||
zoom: number
|
||||
isPerspective: boolean
|
||||
target: Vector3
|
||||
}
|
||||
|
||||
export type ReactCameraProperties =
|
||||
| {
|
||||
type: 'perspective'
|
||||
fov?: number
|
||||
position: [number, number, number]
|
||||
quaternion: [number, number, number, number]
|
||||
}
|
||||
| {
|
||||
type: 'orthographic'
|
||||
zoom?: number
|
||||
position: [number, number, number]
|
||||
quaternion: [number, number, number, number]
|
||||
}
|
||||
|
||||
const lastCmdDelay = 50
|
||||
|
||||
const throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => {
|
||||
const cmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
...convertThreeCamValuesToEngineCam(threeValues),
|
||||
},
|
||||
}
|
||||
engineCommandManager.sendSceneCommand(cmd)
|
||||
}, 1000 / 15)
|
||||
|
||||
let lastPerspectiveCmd: EngineCommand | null = null
|
||||
let lastPerspectiveCmdTime: number = Date.now()
|
||||
let lastPerspectiveCmdTimeoutId: number | null = null
|
||||
|
||||
const sendLastPerspectiveReliableChannel = () => {
|
||||
if (
|
||||
lastPerspectiveCmd &&
|
||||
Date.now() - lastPerspectiveCmdTime >= lastCmdDelay
|
||||
) {
|
||||
engineCommandManager.sendSceneCommand(lastPerspectiveCmd, true)
|
||||
lastPerspectiveCmdTime = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
const throttledUpdateEngineFov = throttle(
|
||||
(vals: {
|
||||
position: Vector3
|
||||
quaternion: Quaternion
|
||||
zoom: number
|
||||
fov: number
|
||||
target: Vector3
|
||||
}) => {
|
||||
const cmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_perspective_settings',
|
||||
...convertThreeCamValuesToEngineCam({
|
||||
...vals,
|
||||
isPerspective: true,
|
||||
}),
|
||||
fov_y: vals.fov,
|
||||
...calculateNearFarFromFOV(vals.fov),
|
||||
},
|
||||
}
|
||||
engineCommandManager.sendSceneCommand(cmd)
|
||||
lastPerspectiveCmd = cmd
|
||||
lastPerspectiveCmdTime = Date.now()
|
||||
if (lastPerspectiveCmdTimeoutId !== null) {
|
||||
clearTimeout(lastPerspectiveCmdTimeoutId)
|
||||
}
|
||||
lastPerspectiveCmdTimeoutId = setTimeout(
|
||||
sendLastPerspectiveReliableChannel,
|
||||
lastCmdDelay
|
||||
) as any as number
|
||||
},
|
||||
1000 / 15
|
||||
)
|
||||
|
||||
export class CameraControls {
|
||||
camera: PerspectiveCamera | OrthographicCamera
|
||||
target: Vector3
|
||||
domElement: HTMLCanvasElement
|
||||
isDragging: boolean
|
||||
mouseDownPosition: Vector2
|
||||
mouseNewPosition: Vector2
|
||||
rotationSpeed = 0.3
|
||||
enableRotate = true
|
||||
enablePan = true
|
||||
enableZoom = true
|
||||
lastPerspectiveFov: number = 45
|
||||
pendingZoom: number | null = null
|
||||
pendingRotation: Vector2 | null = null
|
||||
pendingPan: Vector2 | null = null
|
||||
interactionGuards: MouseGuard = {
|
||||
pan: {
|
||||
description: 'Right click + Shift + drag or middle click + drag',
|
||||
callback: (e) => !!(e.buttons & 4) && !e.ctrlKey,
|
||||
},
|
||||
zoom: {
|
||||
description: 'Scroll wheel or Right click + Ctrl + drag',
|
||||
dragCallback: (e) => e.button === 2 && e.ctrlKey,
|
||||
scrollCallback: () => true,
|
||||
},
|
||||
rotate: {
|
||||
description: 'Right click + drag',
|
||||
callback: (e) => {
|
||||
console.log('event', e)
|
||||
return !!(e.buttons & 2)
|
||||
},
|
||||
},
|
||||
}
|
||||
isFovAnimationInProgress = false
|
||||
fovBeforeOrtho = 45
|
||||
get isPerspective() {
|
||||
return this.camera instanceof PerspectiveCamera
|
||||
}
|
||||
|
||||
// reacts hooks into some of this singleton's properties
|
||||
reactCameraProperties: ReactCameraProperties = {
|
||||
type: 'perspective',
|
||||
fov: 12,
|
||||
position: [0, 0, 0],
|
||||
quaternion: [0, 0, 0, 1],
|
||||
}
|
||||
|
||||
setCam = (camProps: ReactCameraProperties) => {
|
||||
if (
|
||||
camProps.type === 'perspective' &&
|
||||
this.camera instanceof OrthographicCamera
|
||||
) {
|
||||
this.usePerspectiveCamera()
|
||||
} else if (
|
||||
camProps.type === 'orthographic' &&
|
||||
this.camera instanceof PerspectiveCamera
|
||||
) {
|
||||
this.useOrthographicCamera()
|
||||
}
|
||||
this.camera.position.set(...camProps.position)
|
||||
this.camera.quaternion.set(...camProps.quaternion)
|
||||
if (
|
||||
camProps.type === 'perspective' &&
|
||||
this.camera instanceof PerspectiveCamera
|
||||
) {
|
||||
// not sure what to do here, calling dollyZoom here is buggy because it updates the position
|
||||
// at the same time
|
||||
} else if (
|
||||
camProps.type === 'orthographic' &&
|
||||
this.camera instanceof OrthographicCamera
|
||||
) {
|
||||
this.camera.zoom = camProps.zoom || 1
|
||||
}
|
||||
this.camera.updateProjectionMatrix()
|
||||
this.update(true)
|
||||
}
|
||||
|
||||
constructor(isOrtho = false, domElement: HTMLCanvasElement) {
|
||||
this.camera = isOrtho ? new OrthographicCamera() : new PerspectiveCamera()
|
||||
this.camera.up.set(0, 0, 1)
|
||||
this.camera.far = 20000
|
||||
this.target = new Vector3()
|
||||
this.domElement = domElement
|
||||
this.isDragging = false
|
||||
this.mouseDownPosition = new Vector2()
|
||||
this.mouseNewPosition = new Vector2()
|
||||
|
||||
this.domElement.addEventListener('pointerdown', this.onMouseDown)
|
||||
this.domElement.addEventListener('pointermove', this.onMouseMove)
|
||||
this.domElement.addEventListener('pointerup', this.onMouseUp)
|
||||
this.domElement.addEventListener('wheel', this.onMouseWheel)
|
||||
|
||||
window.addEventListener('resize', this.onWindowResize)
|
||||
this.onWindowResize()
|
||||
|
||||
this.update()
|
||||
}
|
||||
|
||||
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
|
||||
() => {}
|
||||
setIsCamMovingCallback(cb: (isMoving: boolean, isTween: boolean) => void) {
|
||||
this._isCamMovingCallback = cb
|
||||
}
|
||||
private _camChangeCallbacks: { [key: string]: () => void } = {}
|
||||
subscribeToCamChange(cb: () => void) {
|
||||
const cbId = uuidv4()
|
||||
this._camChangeCallbacks[cbId] = cb
|
||||
const unsubscribe = () => {
|
||||
delete this._camChangeCallbacks[cbId]
|
||||
}
|
||||
return unsubscribe
|
||||
}
|
||||
|
||||
onWindowResize = () => {
|
||||
if (this.camera instanceof PerspectiveCamera) {
|
||||
this.camera.aspect = window.innerWidth / window.innerHeight
|
||||
} else if (this.camera instanceof OrthographicCamera) {
|
||||
const aspect = window.innerWidth / window.innerHeight
|
||||
this.camera.left = -ORTHOGRAPHIC_CAMERA_SIZE * aspect
|
||||
this.camera.right = ORTHOGRAPHIC_CAMERA_SIZE * aspect
|
||||
this.camera.top = ORTHOGRAPHIC_CAMERA_SIZE
|
||||
this.camera.bottom = -ORTHOGRAPHIC_CAMERA_SIZE
|
||||
}
|
||||
this.camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
onMouseDown = (event: MouseEvent) => {
|
||||
this.isDragging = true
|
||||
this.mouseDownPosition.set(event.clientX, event.clientY)
|
||||
}
|
||||
|
||||
onMouseMove = (event: MouseEvent) => {
|
||||
if (this.isDragging) {
|
||||
this.mouseNewPosition.set(event.clientX, event.clientY)
|
||||
const deltaMove = this.mouseNewPosition
|
||||
.clone()
|
||||
.sub(this.mouseDownPosition)
|
||||
this.mouseDownPosition.copy(this.mouseNewPosition)
|
||||
|
||||
let state: 'pan' | 'rotate' | 'zoom' = 'pan'
|
||||
|
||||
if (this.interactionGuards.pan.callback(event as any)) {
|
||||
if (this.enablePan === false) return
|
||||
// handleMouseDownPan(event)
|
||||
state = 'pan'
|
||||
} else if (this.interactionGuards.rotate.callback(event as any)) {
|
||||
if (this.enableRotate === false) return
|
||||
// handleMouseDownRotate(event)
|
||||
state = 'rotate'
|
||||
} else if (this.interactionGuards.zoom.dragCallback(event as any)) {
|
||||
if (this.enableZoom === false) return
|
||||
// handleMouseDownDolly(event)
|
||||
state = 'zoom'
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
// Implement camera movement logic here based on deltaMove
|
||||
// For example, for rotating the camera around the target:
|
||||
if (state === 'rotate') {
|
||||
this.pendingRotation = this.pendingRotation
|
||||
? this.pendingRotation
|
||||
: new Vector2()
|
||||
this.pendingRotation.x += deltaMove.x
|
||||
this.pendingRotation.y += deltaMove.y
|
||||
} else if (state === 'zoom') {
|
||||
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
||||
this.pendingZoom *= 1 + deltaMove.y * 0.01
|
||||
} else if (state === 'pan') {
|
||||
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
|
||||
let distance = this.camera.position.distanceTo(this.target)
|
||||
if (this.camera instanceof OrthographicCamera) {
|
||||
const zoomFudgeFactor = 2280
|
||||
distance = zoomFudgeFactor / (this.camera.zoom * 45)
|
||||
}
|
||||
const panSpeed = (distance / 1000 / 45) * this.fovBeforeOrtho
|
||||
this.pendingPan.x += -deltaMove.x * panSpeed
|
||||
this.pendingPan.y += deltaMove.y * panSpeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUp = (event: MouseEvent) => {
|
||||
this.isDragging = false
|
||||
}
|
||||
|
||||
onMouseWheel = (event: WheelEvent) => {
|
||||
// Assume trackpad if the deltas are small and integers
|
||||
const isTrackpad = Math.abs(event.deltaY) <= 1 || event.deltaY % 1 === 0
|
||||
|
||||
const zoomSpeed = isTrackpad ? 0.02 : 0.1 // Reduced zoom speed for trackpad
|
||||
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
||||
this.pendingZoom *= 1 + (event.deltaY > 0 ? zoomSpeed : -zoomSpeed)
|
||||
}
|
||||
|
||||
useOrthographicCamera = () => {
|
||||
if (this.camera instanceof OrthographicCamera) return
|
||||
const { x: px, y: py, z: pz } = this.camera.position
|
||||
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
||||
const aspect = window.innerWidth / window.innerHeight
|
||||
this.lastPerspectiveFov = this.camera.fov
|
||||
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||
this.camera = new OrthographicCamera(
|
||||
-ORTHOGRAPHIC_CAMERA_SIZE * aspect,
|
||||
ORTHOGRAPHIC_CAMERA_SIZE * aspect,
|
||||
ORTHOGRAPHIC_CAMERA_SIZE,
|
||||
-ORTHOGRAPHIC_CAMERA_SIZE,
|
||||
z_near,
|
||||
z_far
|
||||
)
|
||||
this.camera.up.set(0, 0, 1)
|
||||
this.camera.layers.enable(SKETCH_LAYER)
|
||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||
this.camera.position.set(px, py, pz)
|
||||
const distance = this.camera.position.distanceTo(this.target.clone())
|
||||
const fovFactor = 45 / this.lastPerspectiveFov
|
||||
this.camera.zoom = (ZOOM_MAGIC_NUMBER * fovFactor * 0.8) / distance
|
||||
|
||||
this.camera.quaternion.set(qx, qy, qz, qw)
|
||||
this.camera.updateProjectionMatrix()
|
||||
engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_set_orthographic',
|
||||
},
|
||||
})
|
||||
this.onCameraChange()
|
||||
}
|
||||
private createPerspectiveCamera = () => {
|
||||
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||
this.camera = new PerspectiveCamera(
|
||||
this.lastPerspectiveFov,
|
||||
window.innerWidth / window.innerHeight,
|
||||
z_near,
|
||||
z_far
|
||||
)
|
||||
this.camera.up.set(0, 0, 1)
|
||||
this.camera.layers.enable(SKETCH_LAYER)
|
||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||
|
||||
return this.camera
|
||||
}
|
||||
usePerspectiveCamera = () => {
|
||||
const { x: px, y: py, z: pz } = this.camera.position
|
||||
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
||||
const zoom = this.camera.zoom
|
||||
this.camera = this.createPerspectiveCamera()
|
||||
|
||||
this.camera.position.set(px, py, pz)
|
||||
this.camera.quaternion.set(qx, qy, qz, qw)
|
||||
const zoomFudgeFactor = 2280
|
||||
const distance = zoomFudgeFactor / (zoom * this.lastPerspectiveFov)
|
||||
const direction = new Vector3().subVectors(
|
||||
this.camera.position,
|
||||
this.target
|
||||
)
|
||||
direction.normalize()
|
||||
this.camera.position.copy(this.target).addScaledVector(direction, distance)
|
||||
|
||||
engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_set_perspective',
|
||||
parameters: {
|
||||
fov_y: this.camera.fov,
|
||||
...calculateNearFarFromFOV(this.lastPerspectiveFov),
|
||||
},
|
||||
},
|
||||
})
|
||||
this.onCameraChange()
|
||||
this.update()
|
||||
return this.camera
|
||||
}
|
||||
|
||||
dollyZoom = (newFov: number) => {
|
||||
if (!(this.camera instanceof PerspectiveCamera)) {
|
||||
console.warn('Dolly zoom is only applicable to perspective cameras.')
|
||||
return
|
||||
}
|
||||
this.lastPerspectiveFov = newFov
|
||||
|
||||
// Calculate the direction vector from the camera towards the controls target
|
||||
const direction = new Vector3()
|
||||
.subVectors(this.target, this.camera.position)
|
||||
.normalize()
|
||||
|
||||
// Calculate the distance to the controls target before changing the FOV
|
||||
const distanceBefore = this.camera.position.distanceTo(this.target)
|
||||
|
||||
// Calculate the scale factor for the new FOV compared to the old one
|
||||
// This needs to be calculated before updating the camera's FOV
|
||||
const oldFov = this.camera.fov
|
||||
|
||||
const viewHeightFactor = (fov: number) => {
|
||||
/* *
|
||||
/|
|
||||
/ |
|
||||
/ |
|
||||
/ |
|
||||
/ | viewHeight/2
|
||||
/ |
|
||||
/ |
|
||||
/↙️fov/2 |
|
||||
/________|
|
||||
\ |
|
||||
\._._._.|
|
||||
*/
|
||||
return Math.tan(deg2Rad(fov / 2))
|
||||
}
|
||||
const scaleFactor = viewHeightFactor(oldFov) / viewHeightFactor(newFov)
|
||||
|
||||
this.camera.fov = newFov
|
||||
this.camera.updateProjectionMatrix()
|
||||
|
||||
const distanceAfter = distanceBefore * scaleFactor
|
||||
|
||||
const newPosition = this.target
|
||||
.clone()
|
||||
.add(direction.multiplyScalar(-distanceAfter))
|
||||
this.camera.position.copy(newPosition)
|
||||
|
||||
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||
this.camera.near = z_near
|
||||
this.camera.far = z_far
|
||||
|
||||
throttledUpdateEngineFov({
|
||||
fov: newFov,
|
||||
position: newPosition,
|
||||
quaternion: this.camera.quaternion,
|
||||
zoom: this.camera.zoom,
|
||||
target: this.target,
|
||||
})
|
||||
}
|
||||
|
||||
update = (forceUpdate = false) => {
|
||||
// If there are any changes that need to be applied to the camera, apply them here.
|
||||
|
||||
let didChange = forceUpdate
|
||||
if (this.pendingRotation) {
|
||||
this.rotateCamera(this.pendingRotation.x, this.pendingRotation.y)
|
||||
this.pendingRotation = null // Clear the pending rotation after applying it
|
||||
didChange = true
|
||||
}
|
||||
|
||||
if (this.pendingZoom) {
|
||||
if (this.camera instanceof PerspectiveCamera) {
|
||||
// move camera towards or away from the target
|
||||
const distance = this.camera.position.distanceTo(this.target)
|
||||
const newDistance = distance * this.pendingZoom
|
||||
const direction = this.camera.position
|
||||
.clone()
|
||||
.sub(this.target)
|
||||
.normalize()
|
||||
const newPosition = this.target
|
||||
.clone()
|
||||
.add(direction.multiplyScalar(newDistance))
|
||||
this.camera.position.copy(newPosition)
|
||||
|
||||
this.camera.updateProjectionMatrix()
|
||||
this.pendingZoom = null // Clear the pending zoom after applying it
|
||||
} else {
|
||||
// TODO change ortho zoom
|
||||
this.camera.zoom = this.camera.zoom / this.pendingZoom
|
||||
this.pendingZoom = null
|
||||
}
|
||||
didChange = true
|
||||
}
|
||||
|
||||
if (this.pendingPan) {
|
||||
// move camera left/right and up/down
|
||||
const offset = this.camera.position.clone().sub(this.target)
|
||||
const direction = offset.clone().normalize()
|
||||
const cameraQuaternion = this.camera.quaternion
|
||||
const up = new Vector3(0, 1, 0).applyQuaternion(cameraQuaternion)
|
||||
const right = new Vector3().crossVectors(up, direction)
|
||||
right.multiplyScalar(this.pendingPan.x)
|
||||
up.multiplyScalar(this.pendingPan.y)
|
||||
const newPosition = this.camera.position.clone().add(right).add(up)
|
||||
this.target.add(right)
|
||||
this.target.add(up)
|
||||
this.camera.position.copy(newPosition)
|
||||
this.pendingPan = null
|
||||
didChange = true
|
||||
}
|
||||
|
||||
this.safeLookAtTarget()
|
||||
|
||||
// Update the camera's matrices
|
||||
this.camera.updateMatrixWorld()
|
||||
if (didChange) {
|
||||
this.onCameraChange()
|
||||
}
|
||||
|
||||
// damping would be implemented here in update if we choose to add it.
|
||||
}
|
||||
|
||||
rotateCamera = (deltaX: number, deltaY: number) => {
|
||||
const quat = new Quaternion().setFromUnitVectors(
|
||||
new Vector3(0, 0, 1),
|
||||
new Vector3(0, 1, 0)
|
||||
)
|
||||
const quatInverse = quat.clone().invert()
|
||||
|
||||
const angleX = deltaX * this.rotationSpeed // rotationSpeed is a constant that defines how fast the camera rotates
|
||||
const angleY = deltaY * this.rotationSpeed
|
||||
|
||||
// Convert angles to radians
|
||||
const radianX = MathUtils.degToRad(angleX)
|
||||
const radianY = MathUtils.degToRad(angleY)
|
||||
|
||||
// Get the offset from the camera to the target
|
||||
const offset = new Vector3().subVectors(this.camera.position, this.target)
|
||||
|
||||
// spherical is a y-up paradigm, need to conform to that for now
|
||||
offset.applyQuaternion(quat)
|
||||
|
||||
// Convert offset to spherical coordinates
|
||||
const spherical = new Spherical().setFromVector3(offset)
|
||||
|
||||
// Apply the rotations
|
||||
spherical.theta -= radianX
|
||||
spherical.phi -= radianY
|
||||
|
||||
// Restrict the phi angle to avoid the camera flipping at the poles
|
||||
spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi))
|
||||
|
||||
// Convert back to Cartesian coordinates
|
||||
offset.setFromSpherical(spherical)
|
||||
|
||||
// put the offset back into the z-up paradigm
|
||||
offset.applyQuaternion(quatInverse)
|
||||
|
||||
// Update the camera's position
|
||||
this.camera.position.copy(this.target).add(offset)
|
||||
|
||||
// Look at the target
|
||||
this.camera.updateMatrixWorld()
|
||||
}
|
||||
|
||||
safeLookAtTarget(up = new Vector3(0, 0, 1)) {
|
||||
const quaternion = _lookAt(this.camera.position, this.target, up)
|
||||
this.camera.quaternion.copy(quaternion)
|
||||
this.camera.updateMatrixWorld()
|
||||
}
|
||||
|
||||
tweenCamToNegYAxis(
|
||||
// -90 degrees from the x axis puts the camera on the negative y axis
|
||||
targetAngle = -Math.PI / 2,
|
||||
duration = 500
|
||||
): Promise<void> {
|
||||
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
|
||||
// zPosition should stay the same
|
||||
const xyRadius = Math.sqrt(
|
||||
(this.target.x - this.camera.position.x) ** 2 +
|
||||
(this.target.y - this.camera.position.y) ** 2
|
||||
)
|
||||
const xyAngle = Math.atan2(
|
||||
this.camera.position.y - this.target.y,
|
||||
this.camera.position.x - this.target.x
|
||||
)
|
||||
this._isCamMovingCallback(true, true)
|
||||
return new Promise((resolve) => {
|
||||
new TWEEN.Tween({ angle: xyAngle })
|
||||
.to({ angle: targetAngle }, duration)
|
||||
.onUpdate((obj) => {
|
||||
const x = xyRadius * Math.cos(obj.angle)
|
||||
const y = xyRadius * Math.sin(obj.angle)
|
||||
this.camera.position.set(
|
||||
this.target.x + x,
|
||||
this.target.y + y,
|
||||
this.camera.position.z
|
||||
)
|
||||
this.update()
|
||||
this.onCameraChange()
|
||||
})
|
||||
.onComplete((obj) => {
|
||||
const x = xyRadius * Math.cos(obj.angle)
|
||||
const y = xyRadius * Math.sin(obj.angle)
|
||||
this.camera.position.set(
|
||||
this.target.x + x,
|
||||
this.target.y + y,
|
||||
this.camera.position.z
|
||||
)
|
||||
this.update()
|
||||
this.onCameraChange()
|
||||
this._isCamMovingCallback(false, true)
|
||||
|
||||
// resolve after a couple of frames
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => resolve())
|
||||
})
|
||||
})
|
||||
.start()
|
||||
})
|
||||
}
|
||||
|
||||
async tweenCameraToQuaternion(
|
||||
targetQuaternion: Quaternion,
|
||||
duration = 500,
|
||||
toOrthographic = true
|
||||
): Promise<void> {
|
||||
const isVertical = isQuaternionVertical(targetQuaternion)
|
||||
let remainingDuration = duration
|
||||
if (isVertical) {
|
||||
remainingDuration = duration * 0.5
|
||||
const orbitRotationDuration = duration * 0.65
|
||||
let targetAngle = -Math.PI / 2
|
||||
const v = new Vector3(0, 0, 1).applyQuaternion(targetQuaternion)
|
||||
if (v.z < 0) targetAngle = Math.PI / 2
|
||||
await this.tweenCamToNegYAxis(targetAngle, orbitRotationDuration)
|
||||
}
|
||||
await this._tweenCameraToQuaternion(
|
||||
targetQuaternion,
|
||||
remainingDuration,
|
||||
toOrthographic
|
||||
)
|
||||
}
|
||||
_tweenCameraToQuaternion(
|
||||
targetQuaternion: Quaternion,
|
||||
duration = 500,
|
||||
toOrthographic = false
|
||||
): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const camera = this.camera
|
||||
this._isCamMovingCallback(true, true)
|
||||
const initialQuaternion = camera.quaternion.clone()
|
||||
const isVertical = isQuaternionVertical(targetQuaternion)
|
||||
let tweenEnd = isVertical ? 0.99 : 1
|
||||
const controlsTarget = this.target.clone()
|
||||
const initialDistance = controlsTarget.distanceTo(camera.position.clone())
|
||||
|
||||
const cameraAtTime = (animationProgress: number /* 0 - 1 */) => {
|
||||
const currentQ = tempQuaternion.slerpQuaternions(
|
||||
initialQuaternion,
|
||||
targetQuaternion,
|
||||
animationProgress
|
||||
)
|
||||
if (this.camera instanceof PerspectiveCamera)
|
||||
// changing the camera position back when it's orthographic doesn't do anything
|
||||
// and it messes up animating back to perspective later
|
||||
this.camera.position
|
||||
.set(0, 0, 1)
|
||||
.applyQuaternion(currentQ)
|
||||
.multiplyScalar(initialDistance)
|
||||
.add(controlsTarget)
|
||||
|
||||
this.camera.up.set(0, 1, 0).applyQuaternion(currentQ).normalize()
|
||||
this.camera.quaternion.copy(currentQ)
|
||||
this.target.copy(controlsTarget)
|
||||
// this.controls.update()
|
||||
this.camera.updateProjectionMatrix()
|
||||
this.update()
|
||||
this.onCameraChange()
|
||||
}
|
||||
|
||||
const onComplete = async () => {
|
||||
if (isReducedMotion() && toOrthographic) {
|
||||
cameraAtTime(0.99)
|
||||
this.useOrthographicCamera()
|
||||
} else if (toOrthographic) {
|
||||
await this.animateToOrthographic()
|
||||
}
|
||||
this.enableRotate = false
|
||||
this._isCamMovingCallback(false, true)
|
||||
resolve()
|
||||
}
|
||||
|
||||
if (isReducedMotion()) {
|
||||
onComplete()
|
||||
return
|
||||
}
|
||||
|
||||
new TWEEN.Tween({ t: 0 })
|
||||
.to({ t: tweenEnd }, duration)
|
||||
.easing(TWEEN.Easing.Quadratic.InOut)
|
||||
.onUpdate(({ t }) => cameraAtTime(t))
|
||||
.onComplete(onComplete)
|
||||
.start()
|
||||
})
|
||||
}
|
||||
|
||||
animateToOrthographic = () =>
|
||||
new Promise((resolve) => {
|
||||
this.isFovAnimationInProgress = true
|
||||
let currentFov = this.lastPerspectiveFov
|
||||
this.fovBeforeOrtho = currentFov
|
||||
|
||||
const targetFov = 4
|
||||
const fovAnimationStep = (currentFov - targetFov) / FRAMES_TO_ANIMATE_IN
|
||||
let frameWaitOnFinish = 10
|
||||
|
||||
const animateFovChange = () => {
|
||||
if (this.camera instanceof PerspectiveCamera) {
|
||||
if (this.camera.fov > targetFov) {
|
||||
// Decrease the FOV
|
||||
currentFov = Math.max(currentFov - fovAnimationStep, targetFov)
|
||||
this.camera.updateProjectionMatrix()
|
||||
this.dollyZoom(currentFov)
|
||||
requestAnimationFrame(animateFovChange) // Continue the animation
|
||||
} else if (frameWaitOnFinish > 0) {
|
||||
frameWaitOnFinish--
|
||||
requestAnimationFrame(animateFovChange) // Continue the animation
|
||||
} else {
|
||||
// Once the target FOV is reached, switch to the orthographic camera
|
||||
// Needs to wait a couple frames after the FOV animation is complete
|
||||
this.useOrthographicCamera()
|
||||
this.isFovAnimationInProgress = false
|
||||
resolve(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animateFovChange() // Start the animation
|
||||
})
|
||||
animateToPerspective = () =>
|
||||
new Promise((resolve) => {
|
||||
this.isFovAnimationInProgress = true
|
||||
// Immediately set the camera to perspective with a very low FOV
|
||||
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
||||
this.lastPerspectiveFov = 4
|
||||
let currentFov = 4
|
||||
this.camera.updateProjectionMatrix()
|
||||
const fovAnimationStep = (targetFov - currentFov) / FRAMES_TO_ANIMATE_IN
|
||||
this.usePerspectiveCamera()
|
||||
|
||||
const animateFovChange = () => {
|
||||
if (this.camera instanceof OrthographicCamera) return
|
||||
if (this.camera.fov < targetFov) {
|
||||
// Increase the FOV
|
||||
currentFov = Math.min(currentFov + fovAnimationStep, targetFov)
|
||||
// this.camera.fov = currentFov
|
||||
this.camera.updateProjectionMatrix()
|
||||
this.dollyZoom(currentFov)
|
||||
requestAnimationFrame(animateFovChange) // Continue the animation
|
||||
} else {
|
||||
// Set the flag to false as the FOV animation is complete
|
||||
this.isFovAnimationInProgress = false
|
||||
resolve(true)
|
||||
}
|
||||
}
|
||||
animateFovChange() // Start the animation
|
||||
})
|
||||
|
||||
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
|
||||
setReactCameraPropertiesCallback = (
|
||||
cb: (a: ReactCameraProperties) => void
|
||||
) => {
|
||||
this.reactCameraPropertiesCallback = cb
|
||||
}
|
||||
|
||||
deferReactUpdate = throttle((a: ReactCameraProperties) => {
|
||||
this.reactCameraPropertiesCallback(a)
|
||||
}, 200)
|
||||
|
||||
onCameraChange = () => {
|
||||
const distance = this.target.distanceTo(this.camera.position)
|
||||
if (this.camera.far / 2.1 < distance || this.camera.far / 1.9 > distance) {
|
||||
this.camera.far = distance * 2
|
||||
this.camera.near = distance / 10
|
||||
this.camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
throttledUpdateEngineCamera({
|
||||
quaternion: this.camera.quaternion,
|
||||
position: this.camera.position,
|
||||
zoom: this.camera.zoom,
|
||||
isPerspective: this.isPerspective,
|
||||
target: this.target,
|
||||
})
|
||||
this.deferReactUpdate({
|
||||
type: this.isPerspective ? 'perspective' : 'orthographic',
|
||||
[this.isPerspective ? 'fov' : 'zoom']:
|
||||
this.camera instanceof PerspectiveCamera
|
||||
? this.camera.fov
|
||||
: this.camera.zoom,
|
||||
position: [
|
||||
roundOff(this.camera.position.x, 2),
|
||||
roundOff(this.camera.position.y, 2),
|
||||
roundOff(this.camera.position.z, 2),
|
||||
],
|
||||
quaternion: [
|
||||
roundOff(this.camera.quaternion.x, 2),
|
||||
roundOff(this.camera.quaternion.y, 2),
|
||||
roundOff(this.camera.quaternion.z, 2),
|
||||
roundOff(this.camera.quaternion.w, 2),
|
||||
],
|
||||
})
|
||||
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
||||
}
|
||||
}
|
||||
|
||||
// currently duplicated, delete one
|
||||
function calculateNearFarFromFOV(fov: number) {
|
||||
const nearFarRatio = (fov - 3) / (45 - 3)
|
||||
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
|
||||
const z_far = 1000 + nearFarRatio * (100000 - 1000)
|
||||
return { z_near: 0.1, z_far }
|
||||
}
|
||||
|
||||
// currently duplicated, delete one
|
||||
function convertThreeCamValuesToEngineCam({
|
||||
target,
|
||||
position,
|
||||
quaternion,
|
||||
zoom,
|
||||
isPerspective,
|
||||
}: ThreeCamValues): {
|
||||
center: Vector3
|
||||
up: Vector3
|
||||
vantage: Vector3
|
||||
} {
|
||||
// Something to consider is that the orbit controls have a target,
|
||||
// we're kind of deriving the target/lookAtVector here when it might not be needed
|
||||
// leaving for now since it's working but maybe revisit later
|
||||
const euler = new Euler().setFromQuaternion(quaternion, 'XYZ')
|
||||
|
||||
const lookAtVector = new Vector3(0, 0, -1)
|
||||
.applyEuler(euler)
|
||||
.normalize()
|
||||
.add(position)
|
||||
|
||||
const upVector = new Vector3(0, 1, 0).applyEuler(euler).normalize()
|
||||
if (isPerspective) {
|
||||
return {
|
||||
center: target,
|
||||
up: upVector,
|
||||
vantage: position,
|
||||
}
|
||||
}
|
||||
const fudgeFactor2 = zoom * 0.9979224466814468 - 0.03473692325839295
|
||||
const zoomFactor = (-ZOOM_MAGIC_NUMBER + fudgeFactor2) / zoom
|
||||
const direction = lookAtVector.clone().sub(position).normalize()
|
||||
const newVantage = position.clone().add(direction.multiplyScalar(zoomFactor))
|
||||
return {
|
||||
center: lookAtVector,
|
||||
up: upVector,
|
||||
vantage: newVantage,
|
||||
}
|
||||
}
|
||||
|
||||
// Pure function helpers
|
||||
|
||||
function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
||||
// Direction from position to target, normalized.
|
||||
let direction = new Vector3().subVectors(target, position).normalize()
|
||||
|
||||
// Calculate a new "effective" up vector that is orthogonal to the direction.
|
||||
// This step ensures that the up vector does not affect the direction the camera is looking.
|
||||
let right = new Vector3().crossVectors(direction, up).normalize()
|
||||
let orthogonalUp = new Vector3().crossVectors(right, direction).normalize()
|
||||
|
||||
// Create a lookAt matrix using the position, and the recalculated orthogonal up vector.
|
||||
let lookAtMatrix = new Matrix4()
|
||||
lookAtMatrix.lookAt(position, target, orthogonalUp)
|
||||
|
||||
// Create a quaternion from the lookAt matrix.
|
||||
let quaternion = new Quaternion().setFromRotationMatrix(lookAtMatrix)
|
||||
|
||||
return quaternion
|
||||
}
|
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])
|
||||
}
|