Compare commits
51 Commits
kurt-rust-
...
pierremtb/
| Author | SHA1 | Date | |
|---|---|---|---|
| 08b0408be2 | |||
| 77a67b3413 | |||
| 2f474e6f40 | |||
| a185005f5c | |||
| 21c72073a9 | |||
| fe21d66c0d | |||
| e4567b43f8 | |||
| 90e7741e47 | |||
| a60f0d1355 | |||
| 9e336b4654 | |||
| bef7467703 | |||
| 6444fe50e7 | |||
| 93c7a6a293 | |||
| eaadc0e39e | |||
| 933850e13b | |||
| 07b9401f2d | |||
| d1a2bd01ca | |||
| aca13d087b | |||
| fcdde3e482 | |||
| dc5277c48b | |||
| a1df3d0ffc | |||
| 1852e6167b | |||
| 29bf77bb82 | |||
| 3a4e323c45 | |||
| e81b614523 | |||
| 5a5fe3bb95 | |||
| 0710f6e5f2 | |||
| c9d5633647 | |||
| bbe4f50b17 | |||
| e376e397db | |||
| c7c747a109 | |||
| f9419a98b5 | |||
| 999f72bccf | |||
| 9dbe74e008 | |||
| 88d9cdc52b | |||
| 2dd1f0f213 | |||
| dd3d604924 | |||
| b19e89effc | |||
| 416002e84a | |||
| 5c3571226b | |||
| d9eead1641 | |||
| 468e86a864 | |||
| 3f977c7bf9 | |||
| 7d7572b36a | |||
| e05bce6145 | |||
| c58c0e7089 | |||
| 83ece965d9 | |||
| de7e958e22 | |||
| 1beadc063d | |||
| 3bc2a00f4e | |||
| 0f256ea6c5 |
@ -1,95 +1,21 @@
|
||||
name: CI
|
||||
name: build-test-publish-apps
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types: [published]
|
||||
schedule:
|
||||
- cron: '0 4 * * *'
|
||||
# Daily at 04:00 AM UTC
|
||||
# Will checkout the last commit from the default branch (main as of 2023-10-04)
|
||||
- pierremtb/issue2805-push-updater-windows-aarch64
|
||||
|
||||
env:
|
||||
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
CUT_RELEASE_PR: false
|
||||
BUILD_RELEASE: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check-format:
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- run: yarn fmt-check
|
||||
|
||||
check-types:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
- run: yarn xstate:typegen
|
||||
- run: yarn tsc
|
||||
|
||||
|
||||
check-typos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
- name: Install codespell
|
||||
run: |
|
||||
python -m pip install codespell
|
||||
- name: Run codespell
|
||||
run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration.
|
||||
|
||||
|
||||
build-test-web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:ci
|
||||
|
||||
- run: yarn test:nowatch
|
||||
|
||||
|
||||
prepare-json-files:
|
||||
runs-on: ubuntu-latest # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||
outputs:
|
||||
version: ${{ steps.export_version.outputs.version }}
|
||||
steps:
|
||||
@ -100,6 +26,10 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Set dummy version
|
||||
run: |
|
||||
VERSION=99.99.99 yarn bump-jsons
|
||||
|
||||
- name: Set nightly version
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
@ -112,13 +42,11 @@ jobs:
|
||||
'.productName=$name' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
||||
|
||||
- name: Set updater test version
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
echo "$(jq --arg url 'https://dl.zoo.dev/releases/modeling-app/test/last_update.json' \
|
||||
echo "$(jq --arg url 'https://dl.zoo.dev/releases/modeling-app/updater-test/last_update.json' \
|
||||
'.plugins.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ github.event_name == 'schedule' || env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
path: |
|
||||
package.json
|
||||
@ -129,54 +57,21 @@ jobs:
|
||||
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
build-test-apps:
|
||||
build-test-app-macos:
|
||||
needs: [prepare-json-files]
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-14, ubuntu-latest, windows-latest]
|
||||
env:
|
||||
# Specific Apple Universal target for macos
|
||||
TAURI_ARGS_MACOS: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
|
||||
# Only build executable on linux (no appimage or deb)
|
||||
TAURI_ARGS_UBUNTU: ${{ matrix.os == 'ubuntu-latest' && '--bundles' || '' }}
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: github.event_name == 'schedule'
|
||||
|
||||
- name: Copy updated .json files
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
ls -l artifact
|
||||
cp artifact/package.json package.json
|
||||
cp artifact/src-tauri/tauri.conf.json src-tauri/tauri.conf.json
|
||||
cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json
|
||||
|
||||
- name: Update WebView2 on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
# Workaround needed to build the tauri windows app with matching edge version.
|
||||
# From https://github.com/actions/runner-images/issues/9538
|
||||
run: |
|
||||
Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
|
||||
Start-Process -FilePath setup.exe -Verb RunAs -Wait
|
||||
|
||||
- name: Install ubuntu system dependencies
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libgtk-3-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
webkit2gtk-driver \
|
||||
libsoup-3.0-dev \
|
||||
libjavascriptcoregtk-4.1-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
at-spi2-core \
|
||||
xvfb
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@ -197,60 +92,25 @@ jobs:
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- name: Run build:wasm manually
|
||||
shell: bash
|
||||
env:
|
||||
MODE: ${{ env.BUILD_RELEASE == 'true' && '--release' || '--debug' }}
|
||||
run: |
|
||||
mkdir src/wasm-lib/pkg; cd src/wasm-lib
|
||||
echo "building with ${{ env.MODE }}"
|
||||
npx wasm-pack build --target web --out-dir pkg ${{ env.MODE }}
|
||||
cd ../../
|
||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
- name: Run build:wasm
|
||||
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
|
||||
|
||||
- name: Run vite build (build:both)
|
||||
- name: Run vite build
|
||||
run: yarn vite build --mode ${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||
|
||||
- name: Fix format
|
||||
run: yarn fmt
|
||||
|
||||
- name: Install x86 target for Universal builds (MacOS only)
|
||||
if: matrix.os == 'macos-14'
|
||||
- name: Install x86 target for Universal builds
|
||||
run: |
|
||||
rustup target add x86_64-apple-darwin
|
||||
|
||||
- name: Prepare certificate and variables (Windows only)
|
||||
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
|
||||
run: |
|
||||
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
|
||||
cat /d/Certificate_pkcs12.p12
|
||||
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
|
||||
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
|
||||
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
|
||||
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
|
||||
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
|
||||
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- name: Setup certicate with SSM KSP (Windows only)
|
||||
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
|
||||
run: |
|
||||
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
|
||||
msiexec /i smtools-windows-x64.msi /quiet /qn
|
||||
smksp_registrar.exe list
|
||||
smctl.exe keypair ls
|
||||
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||
smksp_cert_sync.exe
|
||||
shell: cmd
|
||||
|
||||
- name: Build the app (debug)
|
||||
if: ${{ env.BUILD_RELEASE == 'false' }}
|
||||
run: "yarn tauri build --debug ${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
|
||||
run: "yarn tauri build --debug --target universal-apple-darwin"
|
||||
|
||||
- name: Build for Mac TestFlight (nightly)
|
||||
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
shell: bash
|
||||
run: |
|
||||
unset APPLE_SIGNING_IDENTITY
|
||||
@ -314,7 +174,7 @@ jobs:
|
||||
|
||||
- name: 'Upload to Mac TestFlight (nightly)'
|
||||
uses: apple-actions/upload-testflight-build@v1
|
||||
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
with:
|
||||
app-path: 'src-tauri/target/universal-apple-darwin/release/bundle/macos/Zoo Modeling App.pkg'
|
||||
issuer-id: ${{ secrets.APPLE_STORE_ISSUER_ID }}
|
||||
@ -324,7 +184,7 @@ jobs:
|
||||
|
||||
|
||||
- name: Clean up after Mac TestFlight (nightly)
|
||||
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
shell: bash
|
||||
run: |
|
||||
git status
|
||||
@ -350,35 +210,155 @@ jobs:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
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' }}"
|
||||
run: "yarn tauri build ${{ env.TAURI_CONF_ARGS }} ${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
|
||||
run: "yarn tauri build --config src-tauri/tauri.release.conf.json --target universal-apple-darwin"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
env:
|
||||
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/*/*"
|
||||
path: "src-tauri/target/universal-apple-darwin/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/bundle/*/*"
|
||||
|
||||
- name: Run e2e tests (linux only)
|
||||
if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
- uses: actions/download-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
|
||||
- name: Copy updated .json file for updater test
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: "cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json"
|
||||
|
||||
- name: Build the app (release, updater test)
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: "yarn tauri build -c src-tauri/tauri.release.conf.json -b dmg --target universal-apple-darwin"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
path: "src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg"
|
||||
name: updater-test
|
||||
|
||||
|
||||
build-test-app-windows:
|
||||
needs: [prepare-json-files]
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
|
||||
- name: Copy updated .json files
|
||||
run: |
|
||||
cargo install tauri-driver --force
|
||||
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||
export VITE_KC_API_BASE_URL
|
||||
xvfb-run yarn test:e2e:tauri
|
||||
env:
|
||||
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 }}
|
||||
ls -l artifact
|
||||
cp artifact/package.json package.json
|
||||
cp artifact/src-tauri/tauri.conf.json src-tauri/tauri.conf.json
|
||||
cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json
|
||||
|
||||
- name: Run e2e tests (windows only)
|
||||
if: ${{ matrix.os == 'windows-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
- name: Update WebView2 on Windows
|
||||
# Workaround needed to build the tauri windows app with matching edge version.
|
||||
# From https://github.com/actions/runner-images/issues/9538
|
||||
run: |
|
||||
Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
|
||||
Start-Process -FilePath setup.exe -Verb RunAs -Wait
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- name: Install aarch64 target
|
||||
run: rustup target add aarch64-pc-windows-msvc
|
||||
|
||||
- name: Run build:wasm manually
|
||||
shell: bash
|
||||
env:
|
||||
MODE: ${{ env.BUILD_RELEASE == 'true' && '--release' || '--debug' }}
|
||||
run: |
|
||||
mkdir src/wasm-lib/pkg; cd src/wasm-lib
|
||||
echo "building with ${{ env.MODE }}"
|
||||
npx wasm-pack build --target web --out-dir pkg ${{ env.MODE }}
|
||||
cd ../../
|
||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
|
||||
- name: Run vite build
|
||||
run: yarn vite build --mode ${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||
|
||||
- name: Fix format
|
||||
run: yarn fmt
|
||||
|
||||
- name: Prepare certificate and variables (Windows only)
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
run: |
|
||||
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
|
||||
cat /d/Certificate_pkcs12.p12
|
||||
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
|
||||
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
|
||||
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
|
||||
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
|
||||
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
|
||||
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- name: Setup certicate with SSM KSP (Windows only)
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
run: |
|
||||
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
|
||||
msiexec /i smtools-windows-x64.msi /quiet /qn
|
||||
smksp_registrar.exe list
|
||||
smctl.exe keypair ls
|
||||
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||
smksp_cert_sync.exe
|
||||
shell: cmd
|
||||
|
||||
- name: Build the app (debug) for x86_64
|
||||
if: ${{ env.BUILD_RELEASE == 'false' }}
|
||||
run: "yarn run tauri build --debug"
|
||||
|
||||
- name: Build the app (release) and sign for x86_64
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
run: "yarn tauri build --config src-tauri\\tauri.release.conf.json"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: "src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/bundle/*/*"
|
||||
|
||||
- name: Build the app (debug) for aarch64
|
||||
if: ${{ env.BUILD_RELEASE == 'false' }}
|
||||
run: yarn run tauri build --debug --target aarch64-pc-windows-msvc
|
||||
|
||||
- name: Build the app (release) and sign for aarch64
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
run: "yarn tauri build --config src-tauri\\tauri.release.conf.json --target aarch64-pc-windows-msvc"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: "src-tauri/target/aarch64-pc-windows-msvc/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/bundle/*/*"
|
||||
|
||||
- name: Run e2e tests
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: |
|
||||
cargo install tauri-driver --force
|
||||
yarn wdio run wdio.conf.ts
|
||||
env:
|
||||
E2E_APPLICATION: ".\\src-tauri\\target\\${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}\\Zoo Modeling App.exe"
|
||||
E2E_APPLICATION: ".\\src-tauri\\target\\${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}\\zoo-modeling-app.exe"
|
||||
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_API_BASE_URL: ${{ env.BUILD_RELEASE == 'true' && 'https://api.zoo.dev' || 'https://api.dev.zoo.dev' }}
|
||||
E2E_TAURI_ENABLED: true
|
||||
@ -389,39 +369,32 @@ jobs:
|
||||
|
||||
- name: Copy updated .json file for updater test
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
ls -l artifact
|
||||
cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json
|
||||
cat src-tauri/tauri.release.conf.json
|
||||
run: "cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json"
|
||||
|
||||
- name: Build the app (release, updater test)
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' && matrix.os != 'ubuntu-latest' }}
|
||||
env:
|
||||
TAURI_CONF_ARGS: "-c ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
|
||||
TAURI_BUNDLE_ARGS: "-b ${{ matrix.os == 'windows-latest' && 'msi' || 'dmg' }}"
|
||||
run: "yarn tauri build ${{ env.TAURI_CONF_ARGS }} ${{ env.TAURI_BUNDLE_ARGS }} ${{ env.TAURI_ARGS_MACOS }}"
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: "yarn tauri build -c src-tauri\\tauri.release.conf.json -b msi"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' && matrix.os != 'ubuntu-latest' }}
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
path: "${{ matrix.os == 'macos-14' && 'src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg' || 'src-tauri/target/release/bundle/msi/*.msi' }}"
|
||||
path: "src-tauri/target/release/bundle/msi/*.msi"
|
||||
name: updater-test
|
||||
|
||||
|
||||
publish-apps-release:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
|
||||
needs: [check-format, check-types, check-typos, build-test-web, prepare-json-files, build-test-apps]
|
||||
needs: [prepare-json-files, build-test-app-macos, build-test-app-windows]
|
||||
env:
|
||||
VERSION_NO_V: ${{ needs.prepare-json-files.outputs.version }}
|
||||
VERSION: ${{ github.event_name == 'release' && format('v{0}', needs.prepare-json-files.outputs.version) || needs.prepare-json-files.outputs.version }}
|
||||
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' }}
|
||||
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
||||
VERSION: ${{ needs.prepare-json-files.outputs.version }}
|
||||
PUB_DATE: ${{ github.event.repository.updated_at }}
|
||||
NOTES: ${{ format('Dummy build, commit {0}', github.sha) }}
|
||||
BUCKET_DIR: ${{ 'dl.kittycad.io/releases/modeling-app/updater-test' }}
|
||||
WEBSITE_DIR: ${{ 'dl.zoo.dev/releases/modeling-app/updater-test' }}
|
||||
URL_CODED_NAME: ${{ 'Zoo%20Modeling%20App' }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
|
||||
@ -429,7 +402,8 @@ jobs:
|
||||
run: |
|
||||
ls -l artifact/*/*oo*
|
||||
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
||||
WINDOWS_SIG=`cat artifact/msi/*.msi.zip.sig`
|
||||
WINDOWS_X86_64_SIG=`cat artifact/msi/*x64*.msi.zip.sig`
|
||||
WINDOWS_AARCH64_SIG=`cat artifact/msi/*arm64*.msi.zip.sig`
|
||||
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
||||
jq --null-input \
|
||||
--arg version "${VERSION}" \
|
||||
@ -437,8 +411,10 @@ jobs:
|
||||
--arg notes "${NOTES}" \
|
||||
--arg darwin_sig "$DARWIN_SIG" \
|
||||
--arg darwin_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}.app.tar.gz" \
|
||||
--arg windows_sig "$WINDOWS_SIG" \
|
||||
--arg windows_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
||||
--arg windows_x86_64_sig "$WINDOWS_X86_64_SIG" \
|
||||
--arg windows_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
||||
--arg windows_aarch64_sig "$WINDOWS_AARCH64_SIG" \
|
||||
--arg windows_aarch64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_arm64_en-US.msi.zip" \
|
||||
'{
|
||||
"version": $version,
|
||||
"pub_date": $pub_date,
|
||||
@ -453,8 +429,12 @@ jobs:
|
||||
"url": $darwin_url
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"signature": $windows_sig,
|
||||
"url": $windows_url
|
||||
"signature": $windows_x86_64_sig,
|
||||
"url": $windows_x86_64_url
|
||||
},
|
||||
"windows-aarch64": {
|
||||
"signature": $windows_aarch64_sig,
|
||||
"url": $windows_aarch64_url
|
||||
}
|
||||
}
|
||||
}' > last_update.json
|
||||
@ -468,7 +448,8 @@ jobs:
|
||||
--arg pub_date "${PUB_DATE}" \
|
||||
--arg notes "${NOTES}" \
|
||||
--arg darwin_url "$RELEASE_DIR/dmg/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_universal.dmg" \
|
||||
--arg windows_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi" \
|
||||
--arg windows_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi" \
|
||||
--arg windows_aarch64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_arm64_en-US.msi" \
|
||||
'{
|
||||
"version": $version,
|
||||
"pub_date": $pub_date,
|
||||
@ -478,7 +459,10 @@ jobs:
|
||||
"url": $darwin_url
|
||||
},
|
||||
"msi-x86_64": {
|
||||
"url": $windows_url
|
||||
"url": $windows_x86_64_url
|
||||
},
|
||||
"msi-aarch64": {
|
||||
"url": $windows_aarch64_url
|
||||
}
|
||||
}
|
||||
}' > last_download.json
|
||||
@ -522,7 +506,7 @@ jobs:
|
||||
|
||||
announce_release:
|
||||
needs: [publish-apps-release]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event_name == 'release'
|
||||
steps:
|
||||
- name: Check out code
|
||||
78
.github/workflows/build-test-web.yml
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
name: build-test-web
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check-format:
|
||||
runs-on: 'ubuntu-22.04'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- run: yarn fmt-check
|
||||
|
||||
check-types:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
- run: yarn xstate:typegen
|
||||
- run: yarn tsc
|
||||
|
||||
|
||||
check-typos:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
- name: Install codespell
|
||||
run: |
|
||||
python -m pip install codespell
|
||||
- name: Run codespell
|
||||
run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration.
|
||||
|
||||
|
||||
build-test-web:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:ci
|
||||
|
||||
- run: yarn test:nowatch
|
||||
2
.github/workflows/create-release.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
|
||||
@ -325,7 +325,7 @@ $env:KITTYCAD_API_TOKEN="<YOUR_KITTYCAD_API_TOKEN>"
|
||||
$env:VITE_KC_API_BASE_URL="https://api.dev.zoo.dev"
|
||||
$env:E2E_TAURI_ENABLED="true"
|
||||
$env:TS_NODE_COMPILER_OPTIONS='{"module": "commonjs"}'
|
||||
$env:E2E_APPLICATION=".\src-tauri\target\debug\Zoo Modeling App.exe"
|
||||
$env:E2E_APPLICATION=".\src-tauri\target\debug\zoo-modeling-app.exe"
|
||||
Stop-Process -Name msedgedriver
|
||||
yarn wdio run wdio.conf.ts
|
||||
```
|
||||
|
||||
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
@ -3099,6 +3099,49 @@ const sketch002 = startSketchOn(extrude001, $seg01)
|
||||
).not.toBeDisabled()
|
||||
})
|
||||
|
||||
test('Fillet button states test', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const selectSegment = () => page.getByText(`line([10, 0], %)`).click()
|
||||
const selectClose = () => page.getByText(`close(%)`).click()
|
||||
const clickEmpty = () => page.mouse.click(950, 100)
|
||||
|
||||
// expect fillet button without any bodies in the scene
|
||||
await selectSegment()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
||||
await clickEmpty()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
||||
|
||||
// test fillet button with the body in the scene
|
||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||
const extrude001 = extrude(10, sketch001)`
|
||||
await u.codeLocator.fill(codeToAdd)
|
||||
await selectSegment()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
await selectClose()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
||||
await clickEmpty()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
})
|
||||
|
||||
const removeAfterFirstParenthesis = (inputString: string) => {
|
||||
const index = inputString.indexOf('(')
|
||||
if (index !== -1) {
|
||||
@ -3500,11 +3543,62 @@ test.describe('Command bar tests', () => {
|
||||
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
|
||||
)
|
||||
})
|
||||
test('Command bar works and can change a setting', async ({ page }) => {
|
||||
|
||||
test('Fillet from command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(-10, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const selectSegment = () => page.getByText(`line([0, -10], %)`).click()
|
||||
|
||||
await selectSegment()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByRole('button', { name: 'Fillet' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toContainText(
|
||||
`fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)`
|
||||
)
|
||||
})
|
||||
|
||||
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'theme',
|
||||
exact: false,
|
||||
})
|
||||
const commandLevelArgButton = page.getByRole('button', { name: 'level' })
|
||||
const commandThemeArgButton = page.getByRole('button', { name: 'value' })
|
||||
// This selector changes after we set the setting
|
||||
let commandOptionInput = page.getByPlaceholder('Select an option')
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -3515,23 +3609,17 @@ test.describe('Command bar tests', () => {
|
||||
.or(page.getByRole('button', { name: '⌘K' }))
|
||||
.click()
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await page.keyboard.press('Escape')
|
||||
cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).not.toBeVisible()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
// Try typing in the command bar
|
||||
await page.keyboard.type('theme')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'Settings · app · theme',
|
||||
})
|
||||
await cmdSearchBar.fill('theme')
|
||||
await expect(themeOption).toBeVisible()
|
||||
await themeOption.click()
|
||||
const themeInput = page.getByPlaceholder('Select an option')
|
||||
@ -3553,6 +3641,24 @@ test.describe('Command bar tests', () => {
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
|
||||
commandOptionInput = page.getByPlaceholder('system')
|
||||
|
||||
// Test case for https://github.com/KittyCAD/modeling-app/issues/2882
|
||||
await commandBarButton.click()
|
||||
await cmdSearchBar.focus()
|
||||
await cmdSearchBar.fill('theme')
|
||||
await themeOption.click()
|
||||
await expect(commandThemeArgButton).toBeDisabled()
|
||||
await commandOptionInput.focus()
|
||||
await commandOptionInput.fill('lig')
|
||||
await commandLevelArgButton.click()
|
||||
await expect(commandLevelArgButton).toBeDisabled()
|
||||
|
||||
// Test case for https://github.com/KittyCAD/modeling-app/issues/2881
|
||||
await commandThemeArgButton.click()
|
||||
await expect(commandThemeArgButton).toBeDisabled()
|
||||
await expect(commandLevelArgButton).toHaveText('level: project')
|
||||
})
|
||||
|
||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||
@ -3577,7 +3683,7 @@ test.describe('Command bar tests', () => {
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
// Try typing in the command bar
|
||||
await page.keyboard.type('theme')
|
||||
await cmdSearchBar.fill('theme')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'Settings · app · theme',
|
||||
})
|
||||
@ -3648,7 +3754,9 @@ test.describe('Command bar tests', () => {
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
// Assert that we're on the distance step
|
||||
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'distance', exact: false })
|
||||
).toBeDisabled()
|
||||
|
||||
// Assert that the an alternative variable name is chosen,
|
||||
// since the default variable name is already in use (distance)
|
||||
@ -3663,11 +3771,12 @@ test.describe('Command bar tests', () => {
|
||||
|
||||
// Review step and argument hotkeys
|
||||
await expect(submitButton).toBeEnabled()
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(submitButton).toBeFocused()
|
||||
await submitButton.press('Backspace')
|
||||
|
||||
// Assert we're back on the distance step
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Distance 5', exact: false })
|
||||
page.getByRole('button', { name: 'distance', exact: false })
|
||||
).toBeDisabled()
|
||||
|
||||
await continueButton.click()
|
||||
@ -3691,6 +3800,47 @@ const extrude001 = extrude(distance001, sketch001)`.replace(
|
||||
) // remove newlines
|
||||
)
|
||||
})
|
||||
|
||||
test('Can switch between sketch tools via command bar', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
||||
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const rectangleToolCommand = page.getByRole('option', {
|
||||
name: 'Rectangle',
|
||||
})
|
||||
const rectangleToolButton = page.getByRole('button', { name: 'Rectangle' })
|
||||
const lineToolCommand = page.getByRole('option', { name: 'Line' })
|
||||
const lineToolButton = page.getByRole('button', { name: 'Line' })
|
||||
const arcToolCommand = page.getByRole('option', { name: 'Tangential Arc' })
|
||||
const arcToolButton = page.getByRole('button', { name: 'Tangential Arc' })
|
||||
|
||||
// Start a sketch
|
||||
await sketchButton.click()
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
// Switch between sketch tools via the command bar
|
||||
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await cmdBarButton.click()
|
||||
await rectangleToolCommand.click()
|
||||
await expect(rectangleToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await cmdBarButton.click()
|
||||
await lineToolCommand.click()
|
||||
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// Click in the scene a couple times to draw a line
|
||||
// so tangential arc is valid
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.move(700, 300, { steps: 5 })
|
||||
await page.mouse.click(700, 300)
|
||||
|
||||
// switch to tangential arc via command bar
|
||||
await cmdBarButton.click()
|
||||
await arcToolCommand.click()
|
||||
await expect(arcToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Regression tests', () => {
|
||||
@ -4642,10 +4792,10 @@ test.describe('Sketch tests', () => {
|
||||
// click extrude
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
|
||||
// sketch selection should already have been made. "Selection 1 face" only show up when the selection has been made already
|
||||
// sketch selection should already have been made. "Selection: 1 face" only show up when the selection has been made already
|
||||
// otherwise the cmdbar would be waiting for a selection.
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Selection 1 face' })
|
||||
page.getByRole('button', { name: 'selection : 1 face', exact: false })
|
||||
).toBeVisible()
|
||||
})
|
||||
test("Existing sketch with bad code delete user's code", async ({ page }) => {
|
||||
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.24.0",
|
||||
"name": "zoo-modeling-app",
|
||||
"version": "0.24.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
@ -113,7 +113,7 @@
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.1",
|
||||
"@playwright/test": "^1.45.1",
|
||||
"@tauri-apps/cli": "==2.0.0-beta.13",
|
||||
"@tauri-apps/cli": "==2.0.0-beta.22",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
"@types/mocha": "^10.0.6",
|
||||
|
||||
@ -93,23 +93,10 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
private doSemanticTokens: boolean = false
|
||||
private doFoldingRanges: boolean = false
|
||||
|
||||
private _defferer = deferExecution((code: string) => {
|
||||
try {
|
||||
// Update the state (not the editor) with the new code.
|
||||
this.client.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: this.getDocUri(),
|
||||
version: this.documentVersion++,
|
||||
},
|
||||
contentChanges: [{ text: code }],
|
||||
})
|
||||
|
||||
this.requestSemanticTokens()
|
||||
this.updateFoldingRanges()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}, this.changesDelay)
|
||||
// When a doc update needs to be sent to the server, this holds the
|
||||
// timeout handle for it. When null, the server has the up-to-date
|
||||
// document.
|
||||
private sendScheduled: number | null = null
|
||||
|
||||
constructor(options: LanguageServerOptions, private view: EditorView) {
|
||||
this.client = options.client
|
||||
@ -152,14 +139,9 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
}
|
||||
|
||||
update(viewUpdate: ViewUpdate) {
|
||||
// If the doc didn't change we can return early.
|
||||
if (!viewUpdate.docChanged) {
|
||||
return
|
||||
if (viewUpdate.docChanged) {
|
||||
this.scheduleSendDoc()
|
||||
}
|
||||
|
||||
this.sendChange({
|
||||
documentText: viewUpdate.state.doc.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -184,16 +166,6 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
this.updateFoldingRanges()
|
||||
}
|
||||
|
||||
async sendChange({ documentText }: { documentText: string }) {
|
||||
if (!this.client.ready) return
|
||||
|
||||
this._defferer(documentText)
|
||||
}
|
||||
|
||||
requestDiagnostics() {
|
||||
this.sendChange({ documentText: this.getDocText() })
|
||||
}
|
||||
|
||||
async requestHoverTooltip(
|
||||
view: EditorView,
|
||||
{ line, character }: { line: number; character: number }
|
||||
@ -204,7 +176,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
)
|
||||
return null
|
||||
|
||||
this.sendChange({ documentText: this.getDocText() })
|
||||
this.ensureDocSent()
|
||||
const result = await this.client.textDocumentHover({
|
||||
textDocument: { uri: this.getDocUri() },
|
||||
position: { line, character },
|
||||
@ -227,6 +199,42 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
return { pos, end, create: (view) => ({ dom }), above: true }
|
||||
}
|
||||
|
||||
scheduleSendDoc() {
|
||||
if (this.sendScheduled != null) window.clearTimeout(this.sendScheduled)
|
||||
this.sendScheduled = window.setTimeout(
|
||||
() => this.sendDoc(),
|
||||
this.changesDelay
|
||||
)
|
||||
}
|
||||
|
||||
sendDoc() {
|
||||
if (this.sendScheduled != null) {
|
||||
window.clearTimeout(this.sendScheduled)
|
||||
this.sendScheduled = null
|
||||
}
|
||||
|
||||
if (!this.client.ready) return
|
||||
try {
|
||||
// Update the state (not the editor) with the new code.
|
||||
this.client.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: this.getDocUri(),
|
||||
version: this.documentVersion++,
|
||||
},
|
||||
contentChanges: [{ text: this.view.state.doc.toString() }],
|
||||
})
|
||||
|
||||
this.requestSemanticTokens()
|
||||
this.updateFoldingRanges()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
ensureDocSent() {
|
||||
if (this.sendScheduled != null) this.sendDoc()
|
||||
}
|
||||
|
||||
async getFoldingRanges(): Promise<LSP.FoldingRange[] | null> {
|
||||
if (
|
||||
!this.doFoldingRanges ||
|
||||
@ -284,13 +292,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
)
|
||||
return null
|
||||
|
||||
this.client.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: this.getDocUri(),
|
||||
version: this.documentVersion++,
|
||||
},
|
||||
contentChanges: [{ text: this.getDocText() }],
|
||||
})
|
||||
this.ensureDocSent()
|
||||
|
||||
const result = await this.client.textDocumentFormatting({
|
||||
textDocument: { uri: this.getDocUri() },
|
||||
@ -330,9 +332,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
)
|
||||
return null
|
||||
|
||||
this.sendChange({
|
||||
documentText: context.state.doc.toString(),
|
||||
})
|
||||
this.ensureDocSent()
|
||||
|
||||
const result = await this.client.textDocumentCompletion({
|
||||
textDocument: { uri: this.getDocUri() },
|
||||
|
||||
1528
src-tauri/Cargo.lock
generated
@ -1,17 +1,18 @@
|
||||
[package]
|
||||
name = "app"
|
||||
name = "zoo-modeling-app"
|
||||
version = "0.1.0"
|
||||
description = "The Zoo Modeling App"
|
||||
authors = ["Zoo Engineers <eng@zoo.dev>"]
|
||||
license = ""
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
default-run = "app"
|
||||
default-run = "zoo-modeling-app"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-beta.18", features = [] }
|
||||
tauri-bundler = { git = "https://github.com/tauri-apps/tauri", rev = "1a88fc1a9b81bd09fc24cf0dfed95d20dd72a1bb" }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
|
||||
@ -80,5 +80,5 @@
|
||||
}
|
||||
},
|
||||
"productName": "Zoo Modeling App",
|
||||
"version": "0.24.0"
|
||||
"version": "0.24.1"
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"bundle": {
|
||||
"createUpdaterArtifacts": "v1Compatible",
|
||||
"windows": {
|
||||
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
|
||||
"digestAlgorithm": "sha256",
|
||||
|
||||
@ -39,3 +39,32 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
||||
</AppStateContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
interface AppStream {
|
||||
mediaStream: MediaStream
|
||||
setMediaStream: (mediaStream: MediaStream) => void
|
||||
}
|
||||
|
||||
const AppStreamContext = createContext<AppStream>({
|
||||
mediaStream: undefined as unknown as MediaStream,
|
||||
setMediaStream: () => {},
|
||||
})
|
||||
|
||||
export const useAppStream = () => useContext(AppStreamContext)
|
||||
|
||||
export const AppStreamProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [mediaStream, setMediaStream] = useState<MediaStream>(
|
||||
undefined as unknown as MediaStream
|
||||
)
|
||||
|
||||
return (
|
||||
<AppStreamContext.Provider
|
||||
value={{
|
||||
mediaStream,
|
||||
setMediaStream,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AppStreamContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
canRectangleTool,
|
||||
isEditingExistingSketch,
|
||||
} from 'machines/modelingMachine'
|
||||
import { DEV } from 'env'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
@ -60,7 +61,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
data: { tool: 'line' },
|
||||
}),
|
||||
{ enabled: !disableLineButton, scopes: ['sketch'] }
|
||||
)
|
||||
@ -75,7 +76,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
data: { tool: 'tangentialArc' },
|
||||
}),
|
||||
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
|
||||
)
|
||||
@ -89,7 +90,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
data: { tool: 'rectangle' },
|
||||
}),
|
||||
{ enabled: !disableRectangle, scopes: ['sketch'] }
|
||||
)
|
||||
@ -114,10 +115,20 @@ export function Toolbar({
|
||||
() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Extrude', ownerMachine: 'modeling' },
|
||||
data: { name: 'Extrude', groupId: 'modeling' },
|
||||
}),
|
||||
{ enabled: !disableAllButtons, scopes: ['modeling'] }
|
||||
)
|
||||
const disableFillet = !state.can('Fillet') || disableAllButtons
|
||||
useHotkeys(
|
||||
'f',
|
||||
() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Fillet', groupId: 'modeling' },
|
||||
}),
|
||||
{ enabled: !disableFillet, scopes: ['modeling'] }
|
||||
)
|
||||
|
||||
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
||||
const span = toolbarButtonsRef.current
|
||||
@ -263,7 +274,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
data: { tool: 'line' },
|
||||
})
|
||||
}
|
||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||
@ -293,7 +304,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
data: { tool: 'tangentialArc' },
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||
@ -323,7 +334,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
data: { tool: 'rectangle' },
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||
@ -378,7 +389,7 @@ export function Toolbar({
|
||||
onClick={() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Extrude', ownerMachine: 'modeling' },
|
||||
data: { name: 'Extrude', groupId: 'modeling' },
|
||||
})
|
||||
}
|
||||
disabled={!state.can('Extrude') || disableAllButtons}
|
||||
@ -404,6 +415,36 @@ export function Toolbar({
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
{state.matches('idle') && (DEV || (window as any)._enableFillet) && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Fillet', groupId: 'modeling' },
|
||||
})
|
||||
}
|
||||
disabled={disableFillet}
|
||||
title={disableFillet ? 'fillet' : "edge can't be filleted"}
|
||||
iconStart={{
|
||||
icon: 'fillet', // todo: add fillet icon
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
>
|
||||
Fillet
|
||||
<Tooltip
|
||||
delay={1250}
|
||||
position="bottom"
|
||||
className="!px-2 !text-xs"
|
||||
>
|
||||
Shortcut: F
|
||||
</Tooltip>
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</menu>
|
||||
)
|
||||
|
||||
@ -41,6 +41,7 @@ function CommandArgOptionInput({
|
||||
)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
const [shouldSubmitOnChange, setShouldSubmitOnChange] = useState(false)
|
||||
const [selectedOption, setSelectedOption] = useState<
|
||||
CommandArgumentOption<unknown>
|
||||
>(currentOption || resolvedOptions[0])
|
||||
@ -82,8 +83,10 @@ function CommandArgOptionInput({
|
||||
// We deal with the whole option object internally
|
||||
setSelectedOption(option)
|
||||
|
||||
// But we only submit the value
|
||||
onSubmit(option.value)
|
||||
// But we only submit the value itself
|
||||
if (shouldSubmitOnChange) {
|
||||
onSubmit(option.value)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
@ -94,7 +97,18 @@ function CommandArgOptionInput({
|
||||
}
|
||||
|
||||
return (
|
||||
<form id="arg-form" onSubmit={handleSubmit} ref={formRef}>
|
||||
<form
|
||||
id="arg-form"
|
||||
onSubmit={handleSubmit}
|
||||
ref={formRef}
|
||||
onKeyDownCapture={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
setShouldSubmitOnChange(true)
|
||||
} else {
|
||||
setShouldSubmitOnChange(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Combobox
|
||||
value={selectedOption}
|
||||
onChange={handleSelectOption}
|
||||
@ -118,6 +132,12 @@ function CommandArgOptionInput({
|
||||
if (event.key === 'Backspace' && !event.currentTarget.value) {
|
||||
stepBack()
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
setShouldSubmitOnChange(true)
|
||||
} else {
|
||||
setShouldSubmitOnChange(false)
|
||||
}
|
||||
}}
|
||||
value={query}
|
||||
placeholder={
|
||||
@ -136,6 +156,9 @@ function CommandArgOptionInput({
|
||||
<Combobox.Options
|
||||
static
|
||||
className="overflow-y-auto max-h-96 cursor-pointer"
|
||||
onMouseDown={() => {
|
||||
setShouldSubmitOnChange(true)
|
||||
}}
|
||||
>
|
||||
{filteredOptions?.map((option) => (
|
||||
<Combobox.Option
|
||||
|
||||
@ -114,6 +114,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
>
|
||||
{argName}
|
||||
</span>
|
||||
<span className="sr-only">: </span>
|
||||
{argValue ? (
|
||||
arg.inputType === 'selection' ? (
|
||||
getSelectionTypeDisplayText(argValue as Selections)
|
||||
|
||||
@ -28,6 +28,11 @@ export const CommandBarProvider = ({
|
||||
Object.keys(context.selectedCommand?.args).length === 0
|
||||
)
|
||||
},
|
||||
'All arguments are skippable': (context, _event) => {
|
||||
return Object.values(context.selectedCommand!.args!).every(
|
||||
(argConfig) => argConfig.skip
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -11,6 +11,25 @@ import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { StateFrom } from 'xstate'
|
||||
|
||||
const semanticEntityNames = {
|
||||
face: ['extrude-wall', 'start-cap', 'end-cap'],
|
||||
edge: ['edge', 'line', 'arc'],
|
||||
point: ['point', 'line-end', 'line-mid'],
|
||||
}
|
||||
|
||||
function getSemanticSelectionType(selectionType: string[]) {
|
||||
const semanticSelectionType = new Set()
|
||||
selectionType.forEach((type) => {
|
||||
Object.entries(semanticEntityNames).forEach(([entity, entityTypes]) => {
|
||||
if (entityTypes.includes(type)) {
|
||||
semanticSelectionType.add(entity)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return Array.from(semanticSelectionType)
|
||||
}
|
||||
|
||||
const selectionSelector = (snapshot: StateFrom<typeof modelingMachine>) =>
|
||||
snapshot.context.selectionRanges
|
||||
|
||||
@ -85,7 +104,9 @@ function CommandBarSelectionInput({
|
||||
>
|
||||
{canSubmitSelection
|
||||
? getSelectionTypeDisplayText(selection) + ' selected'
|
||||
: `Please select ${arg.multiple ? 'one or more faces' : 'one face'}`}
|
||||
: `Please select ${
|
||||
arg.multiple ? 'one or more ' : 'one '
|
||||
}${getSemanticSelectionType(arg.selectionTypes).join(' or ')}`}
|
||||
<input
|
||||
id="selection"
|
||||
name="selection"
|
||||
|
||||
@ -70,19 +70,23 @@ function CommandComboBox({
|
||||
>
|
||||
{filteredOptions?.map((option) => (
|
||||
<Combobox.Option
|
||||
key={option.name}
|
||||
key={option.groupId + option.name + (option.displayName || '')}
|
||||
value={option}
|
||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
>
|
||||
{'icon' in option && option.icon && (
|
||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||
)}
|
||||
<p className="flex-grow">{option.displayName || option.name} </p>
|
||||
{option.description && (
|
||||
<p className="text-xs text-chalkboard-60 dark:text-chalkboard-40">
|
||||
{option.description}
|
||||
<div className="flex-grow flex flex-col">
|
||||
<p className="my-0 leading-tight">
|
||||
{option.displayName || option.name}{' '}
|
||||
</p>
|
||||
)}
|
||||
{option.description && (
|
||||
<p className="my-0 text-xs text-chalkboard-60 dark:text-chalkboard-50">
|
||||
{option.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
|
||||
@ -131,6 +131,16 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
code: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.7071 5L7.77734 14.7794L8.73527 15.0663L11.665 5.28698L10.7071 5ZM2.35356 9.64644L5.85362 6.14644L6.56072 6.85355L3.41423 10L6.56072 13.1464L5.85362 13.8536L2.35356 10.3536L2 10L2.35356 9.64644ZM17.0607 9.64644L13.5607 6.14644L12.8536 6.85355L16 10L12.8536 13.1464L13.5607 13.8535L17.0607 10.3536L17.4142 10L17.0607 9.64644Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
dimension: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
@ -177,6 +187,22 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
fillet: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8 5H5V15H15V12C15 8.13401 11.866 5 8 5ZM5 4H4V5V15V16H5H15H16V15V12C16 7.58172 12.4183 4 8 4H5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.5 3.5H5.5H8.5C12.9183 3.5 16.5 7.08172 16.5 11.5V14.5V15.5H16V12C16 7.58172 12.4182 4 7.99996 4H4.5V3.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
file: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
||||
@ -33,6 +33,7 @@ import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||
import {
|
||||
Selections,
|
||||
canExtrudeSelection,
|
||||
canFilletSelection,
|
||||
handleSelectionBatch,
|
||||
isSelectionLastLine,
|
||||
isRangeInbetweenCharacters,
|
||||
@ -42,7 +43,7 @@ import {
|
||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
import {
|
||||
STRAIGHT_SEGMENT,
|
||||
TANGENTIAL_ARC_TO_SEGMENT,
|
||||
@ -72,6 +73,7 @@ import { uuidv4 } from 'lib/utils'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { modelingMachineEvent } from 'editor/manager'
|
||||
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -444,6 +446,12 @@ export const ModelingMachineProvider = ({
|
||||
if (selectionRanges.codeBasedSelections.length <= 0) return false
|
||||
return true
|
||||
},
|
||||
'has valid fillet selection': ({ selectionRanges }) =>
|
||||
hasValidFilletSelection({
|
||||
selectionRanges,
|
||||
ast: kclManager.ast,
|
||||
code: codeManager.code,
|
||||
}),
|
||||
'Selection is on face': ({ selectionRanges }, { data }) => {
|
||||
if (data?.forceNewSketch) return false
|
||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||
@ -494,7 +502,6 @@ export const ModelingMachineProvider = ({
|
||||
kclManager.ast,
|
||||
data.sketchPathToNode,
|
||||
data.extrudePathToNode,
|
||||
kclManager.programMemory,
|
||||
data.cap
|
||||
)
|
||||
if (trap(sketched)) return Promise.reject(sketched)
|
||||
@ -920,7 +927,7 @@ export const ModelingMachineProvider = ({
|
||||
state: modelingState,
|
||||
send: modelingSend,
|
||||
actor: modelingActor,
|
||||
commandBarConfig: modelingMachineConfig,
|
||||
commandBarConfig: modelingMachineCommandConfig,
|
||||
allCommandsRequireNetwork: true,
|
||||
// TODO for when sketch tools are in the toolbar: This was added when we used one "Cancel" event,
|
||||
// but we need to support "SketchCancel" and basically
|
||||
|
||||
@ -82,11 +82,11 @@ function ProjectMenuPopover({
|
||||
}) {
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const { onProjectClose } = useLspContext()
|
||||
const exportCommandInfo = { name: 'Export', ownerMachine: 'modeling' }
|
||||
const findCommand = (obj: { name: string; ownerMachine: string }) =>
|
||||
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
||||
const findCommand = (obj: { name: string; groupId: string }) =>
|
||||
Boolean(
|
||||
commandBarState.context.commands.find(
|
||||
(c) => c.name === obj.name && c.ownerMachine === obj.ownerMachine
|
||||
(c) => c.name === obj.name && c.groupId === obj.groupId
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { DEV } from 'env'
|
||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||
import { getNormalisedCoordinates } from '../lib/utils'
|
||||
import Loading from './Loading'
|
||||
@ -9,6 +10,7 @@ import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
|
||||
import { btnName } from 'lib/cameraControls'
|
||||
import { sendSelectEventToEngine } from 'lib/selections'
|
||||
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||
import { useAppStream } from 'AppState'
|
||||
|
||||
export const Stream = () => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@ -17,9 +19,12 @@ export const Stream = () => {
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const { mediaStream } = useAppStream()
|
||||
const { overallState } = useNetworkContext()
|
||||
const [isFreezeFrame, setIsFreezeFrame] = useState(false)
|
||||
|
||||
const IDLE = true
|
||||
|
||||
const isNetworkOkay =
|
||||
overallState === NetworkHealthState.Ok ||
|
||||
overallState === NetworkHealthState.Weak
|
||||
@ -51,7 +56,7 @@ export const Stream = () => {
|
||||
capture: true,
|
||||
})
|
||||
|
||||
const IDLE_TIME_MS = 1000 * 20
|
||||
const IDLE_TIME_MS = 1000 * 60 * 2
|
||||
let timeoutIdIdleA: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
const teardown = () => {
|
||||
@ -60,19 +65,21 @@ export const Stream = () => {
|
||||
sceneInfra.modelingSend({ type: 'Cancel' })
|
||||
// Give video time to pause
|
||||
window.requestAnimationFrame(() => {
|
||||
engineCommandManager.engineConnection?.tearDown({ freeze: true })
|
||||
engineCommandManager.tearDown()
|
||||
})
|
||||
}
|
||||
|
||||
// Teardown everything if we go hidden or reconnect
|
||||
if (globalThis?.window?.document) {
|
||||
globalThis.window.document.onvisibilitychange = () => {
|
||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
if (IDLE && DEV) {
|
||||
if (globalThis?.window?.document) {
|
||||
globalThis.window.document.onvisibilitychange = () => {
|
||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,35 +87,44 @@ export const Stream = () => {
|
||||
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
const onAnyInput = () => {
|
||||
if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
}
|
||||
// Clear both timers
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
clearTimeout(timeoutIdIdleB)
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
}
|
||||
|
||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
||||
if (IDLE && DEV) {
|
||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
||||
}
|
||||
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
if (IDLE && DEV) {
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
}
|
||||
|
||||
return () => {
|
||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
||||
capture: true,
|
||||
})
|
||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'touchstart',
|
||||
onAnyInput
|
||||
)
|
||||
if (IDLE && DEV) {
|
||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'mousemove',
|
||||
onAnyInput
|
||||
)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'mousedown',
|
||||
onAnyInput
|
||||
)
|
||||
globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'touchstart',
|
||||
onAnyInput
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@ -124,10 +140,10 @@ export const Stream = () => {
|
||||
)
|
||||
return
|
||||
if (!videoRef.current) return
|
||||
if (!context.store?.mediaStream) return
|
||||
if (!mediaStream) return
|
||||
|
||||
// Do not immediately play the stream!
|
||||
videoRef.current.srcObject = context.store.mediaStream
|
||||
videoRef.current.srcObject = mediaStream
|
||||
videoRef.current.pause()
|
||||
|
||||
send({
|
||||
@ -136,7 +152,7 @@ export const Stream = () => {
|
||||
videoElement: videoRef.current,
|
||||
},
|
||||
})
|
||||
}, [context.store?.mediaStream])
|
||||
}, [mediaStream])
|
||||
|
||||
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
if (!isNetworkOkay) return
|
||||
|
||||
@ -195,14 +195,15 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
private queuedUids: string[] = []
|
||||
|
||||
private _deffererCodeUpdate = deferExecution(() => {
|
||||
this.requestCompletions()
|
||||
}, changesDelay)
|
||||
|
||||
private _deffererUserSelect = deferExecution(() => {
|
||||
this.rejectSuggestionCommand()
|
||||
}, changesDelay)
|
||||
|
||||
// When a doc update needs to be sent to the server, this holds the
|
||||
// timeout handle for it. When null, the server has the up-to-date
|
||||
// document.
|
||||
private sendScheduledInput: number | null = null
|
||||
|
||||
constructor(readonly view: EditorView, client: LanguageServerClient) {
|
||||
this.client = client
|
||||
}
|
||||
@ -245,7 +246,34 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
this.lastPos = this.view.state.selection.main.head
|
||||
if (viewUpdate.docChanged) this._deffererCodeUpdate(true)
|
||||
if (viewUpdate.docChanged) this.scheduleUpdateDoc()
|
||||
}
|
||||
|
||||
scheduleUpdateDoc() {
|
||||
if (this.sendScheduledInput != null)
|
||||
window.clearTimeout(this.sendScheduledInput)
|
||||
this.sendScheduledInput = window.setTimeout(
|
||||
() => this.updateDoc(),
|
||||
changesDelay
|
||||
)
|
||||
}
|
||||
|
||||
updateDoc() {
|
||||
if (this.sendScheduledInput != null) {
|
||||
window.clearTimeout(this.sendScheduledInput)
|
||||
this.sendScheduledInput = null
|
||||
}
|
||||
|
||||
if (!this.client.ready) return
|
||||
try {
|
||||
this.requestCompletions()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
ensureDocUpdated() {
|
||||
if (this.sendScheduledInput != null) this.updateDoc()
|
||||
}
|
||||
|
||||
ghostText(): GhostText | null {
|
||||
|
||||
@ -27,13 +27,10 @@ export class KclPlugin implements PluginValue {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
private _deffererCodeUpdate = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
return
|
||||
}
|
||||
|
||||
kclManager.executeCode()
|
||||
}, changesDelay)
|
||||
// When a doc update needs to be sent to the server, this holds the
|
||||
// timeout handle for it. When null, the server has the up-to-date
|
||||
// document.
|
||||
private sendScheduledInput: number | null = null
|
||||
|
||||
private _deffererUserSelect = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
@ -101,7 +98,34 @@ export class KclPlugin implements PluginValue {
|
||||
codeManager.code = newCode
|
||||
codeManager.writeToFile()
|
||||
|
||||
this._deffererCodeUpdate(true)
|
||||
this.scheduleUpdateDoc()
|
||||
}
|
||||
|
||||
scheduleUpdateDoc() {
|
||||
if (this.sendScheduledInput != null)
|
||||
window.clearTimeout(this.sendScheduledInput)
|
||||
this.sendScheduledInput = window.setTimeout(
|
||||
() => this.updateDoc(),
|
||||
changesDelay
|
||||
)
|
||||
}
|
||||
|
||||
updateDoc() {
|
||||
if (this.sendScheduledInput != null) {
|
||||
window.clearTimeout(this.sendScheduledInput)
|
||||
this.sendScheduledInput = null
|
||||
}
|
||||
|
||||
if (!this.client.ready) return
|
||||
try {
|
||||
kclManager.executeCode()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
ensureDocUpdated() {
|
||||
if (this.sendScheduledInput != null) this.updateDoc()
|
||||
}
|
||||
|
||||
async updateUnits(
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useLayoutEffect, useEffect, useRef } from 'react'
|
||||
import { useLayoutEffect, useEffect, useRef, useState } from 'react'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { makeDefaultPlanes, modifyGrid } from 'lang/wasm'
|
||||
import { useModelingContext } from './useModelingContext'
|
||||
import { useAppState } from 'AppState'
|
||||
import { useAppState, useAppStream } from 'AppState'
|
||||
|
||||
export function useSetupEngineManager(
|
||||
streamRef: React.RefObject<HTMLDivElement>,
|
||||
@ -28,9 +28,7 @@ export function useSetupEngineManager(
|
||||
}
|
||||
) {
|
||||
const { setAppState } = useAppState()
|
||||
|
||||
const streamWidth = streamRef?.current?.offsetWidth
|
||||
const streamHeight = streamRef?.current?.offsetHeight
|
||||
const { setMediaStream } = useAppStream()
|
||||
|
||||
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
||||
|
||||
@ -40,59 +38,60 @@ export function useSetupEngineManager(
|
||||
engineCommandManager.pool = settings.pool
|
||||
}
|
||||
|
||||
const startEngineInstance = () => {
|
||||
const startEngineInstance = (restart: boolean = false) => {
|
||||
// Load the engine command manager once with the initial width and height,
|
||||
// then we do not want to reload it.
|
||||
const { width: quadWidth, height: quadHeight } = getDimensions(
|
||||
streamWidth,
|
||||
streamHeight
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
if (
|
||||
!hasSetNonZeroDimensions.current &&
|
||||
quadHeight &&
|
||||
quadWidth &&
|
||||
settings.modelingSend
|
||||
) {
|
||||
engineCommandManager.start({
|
||||
setMediaStream: (mediaStream) =>
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: { mediaStream },
|
||||
}),
|
||||
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
|
||||
width: quadWidth,
|
||||
height: quadHeight,
|
||||
executeCode: () => {
|
||||
// We only want to execute the code here that we already have set.
|
||||
// Nothing else.
|
||||
kclManager.isFirstRender = true
|
||||
return kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
},
|
||||
token,
|
||||
settings,
|
||||
makeDefaultPlanes: () => {
|
||||
return makeDefaultPlanes(kclManager.engineCommandManager)
|
||||
},
|
||||
modifyGrid: (hidden: boolean) => {
|
||||
return modifyGrid(kclManager.engineCommandManager, hidden)
|
||||
},
|
||||
})
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
streamDimensions: {
|
||||
streamWidth: quadWidth,
|
||||
streamHeight: quadHeight,
|
||||
},
|
||||
},
|
||||
})
|
||||
hasSetNonZeroDimensions.current = true
|
||||
if (restart) {
|
||||
kclManager.isFirstRender = false
|
||||
}
|
||||
engineCommandManager.start({
|
||||
restart,
|
||||
setMediaStream: (mediaStream) => setMediaStream(mediaStream),
|
||||
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
|
||||
width: quadWidth,
|
||||
height: quadHeight,
|
||||
executeCode: () => {
|
||||
// We only want to execute the code here that we already have set.
|
||||
// Nothing else.
|
||||
kclManager.isFirstRender = true
|
||||
return kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
},
|
||||
token,
|
||||
settings,
|
||||
makeDefaultPlanes: () => {
|
||||
return makeDefaultPlanes(kclManager.engineCommandManager)
|
||||
},
|
||||
modifyGrid: (hidden: boolean) => {
|
||||
return modifyGrid(kclManager.engineCommandManager, hidden)
|
||||
},
|
||||
})
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
streamDimensions: {
|
||||
streamWidth: quadWidth,
|
||||
streamHeight: quadHeight,
|
||||
},
|
||||
},
|
||||
})
|
||||
hasSetNonZeroDimensions.current = true
|
||||
}
|
||||
|
||||
useLayoutEffect(startEngineInstance, [
|
||||
useLayoutEffect(() => {
|
||||
const { width: quadWidth, height: quadHeight } = getDimensions(
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
|
||||
startEngineInstance()
|
||||
}
|
||||
}, [
|
||||
streamRef?.current?.offsetWidth,
|
||||
streamRef?.current?.offsetHeight,
|
||||
settings.modelingSend,
|
||||
@ -101,8 +100,8 @@ export function useSetupEngineManager(
|
||||
useEffect(() => {
|
||||
const handleResize = deferExecution(() => {
|
||||
const { width, height } = getDimensions(
|
||||
streamRef?.current?.offsetWidth,
|
||||
streamRef?.current?.offsetHeight
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
if (
|
||||
settings.modelingContext.store.streamDimensions.streamWidth !== width ||
|
||||
@ -125,10 +124,37 @@ export function useSetupEngineManager(
|
||||
}, 500)
|
||||
|
||||
const onOnline = () => {
|
||||
startEngineInstance()
|
||||
startEngineInstance(true)
|
||||
}
|
||||
|
||||
const onVisibilityChange = () => {
|
||||
if (window.document.visibilityState === 'visible') {
|
||||
if (
|
||||
!engineCommandManager.engineConnection?.isReady() &&
|
||||
!engineCommandManager.engineConnection?.isConnecting()
|
||||
) {
|
||||
startEngineInstance()
|
||||
}
|
||||
}
|
||||
}
|
||||
window.document.addEventListener('visibilitychange', onVisibilityChange)
|
||||
|
||||
const onAnyInput = () => {
|
||||
if (
|
||||
!engineCommandManager.engineConnection?.isReady() &&
|
||||
!engineCommandManager.engineConnection?.isConnecting()
|
||||
) {
|
||||
startEngineInstance()
|
||||
}
|
||||
}
|
||||
window.document.addEventListener('keydown', onAnyInput)
|
||||
window.document.addEventListener('mousemove', onAnyInput)
|
||||
window.document.addEventListener('mousedown', onAnyInput)
|
||||
window.document.addEventListener('scroll', onAnyInput)
|
||||
window.document.addEventListener('touchstart', onAnyInput)
|
||||
|
||||
const onOffline = () => {
|
||||
kclManager.isFirstRender = true
|
||||
engineCommandManager.tearDown()
|
||||
}
|
||||
|
||||
@ -136,11 +162,30 @@ export function useSetupEngineManager(
|
||||
window.addEventListener('offline', onOffline)
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => {
|
||||
window.document.removeEventListener(
|
||||
'visibilitychange',
|
||||
onVisibilityChange
|
||||
)
|
||||
window.document.removeEventListener('keydown', onAnyInput)
|
||||
window.document.removeEventListener('mousemove', onAnyInput)
|
||||
window.document.removeEventListener('mousedown', onAnyInput)
|
||||
window.document.removeEventListener('scroll', onAnyInput)
|
||||
window.document.removeEventListener('touchstart', onAnyInput)
|
||||
window.removeEventListener('online', onOnline)
|
||||
window.removeEventListener('offline', onOffline)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Engine relies on many settings so we should rebind events when it changes
|
||||
// We have to list out the ones we care about because the settings object holds
|
||||
// non-settings too...
|
||||
}, [
|
||||
settings.enableSSAO,
|
||||
settings.highlightEdges,
|
||||
settings.showScaleGrid,
|
||||
settings.theme,
|
||||
settings.pool,
|
||||
])
|
||||
}
|
||||
|
||||
function getDimensions(streamWidth?: number, streamHeight?: number) {
|
||||
|
||||
@ -6,7 +6,11 @@ import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { authMachine } from 'machines/authMachine'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
import { homeMachine } from 'machines/homeMachine'
|
||||
import { Command, CommandSetConfig, CommandSetSchema } from 'lib/commandTypes'
|
||||
import {
|
||||
Command,
|
||||
StateMachineCommandSetConfig,
|
||||
StateMachineCommandSetSchema,
|
||||
} from 'lib/commandTypes'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
@ -21,20 +25,20 @@ export type AllMachines =
|
||||
|
||||
interface UseStateMachineCommandsArgs<
|
||||
T extends AllMachines,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
> {
|
||||
machineId: T['id']
|
||||
state: StateFrom<T>
|
||||
send: Function
|
||||
actor: InterpreterFrom<T>
|
||||
commandBarConfig?: CommandSetConfig<T, S>
|
||||
commandBarConfig?: StateMachineCommandSetConfig<T, S>
|
||||
allCommandsRequireNetwork?: boolean
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
export default function useStateMachineCommands<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
>({
|
||||
machineId,
|
||||
state,
|
||||
@ -58,9 +62,10 @@ export default function useStateMachineCommands<
|
||||
const newCommands = state.nextEvents
|
||||
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
|
||||
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
|
||||
.map((type) =>
|
||||
.flatMap((type) =>
|
||||
createMachineCommand<T, S>({
|
||||
ownerMachine: machineId,
|
||||
// The group is the owner machine's ID.
|
||||
groupId: machineId,
|
||||
type,
|
||||
state,
|
||||
send,
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
UpdaterRestartModal,
|
||||
createUpdaterRestartModal,
|
||||
} from 'components/UpdaterRestartModal'
|
||||
import { AppStreamProvider } from 'AppState'
|
||||
|
||||
// uncomment for xstate inspector
|
||||
// import { DEV } from 'env'
|
||||
@ -26,28 +27,30 @@ const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||
|
||||
root.render(
|
||||
<HotkeysProvider>
|
||||
<Router />
|
||||
<Toaster
|
||||
position="bottom-center"
|
||||
toastOptions={{
|
||||
style: {
|
||||
borderRadius: '3px',
|
||||
},
|
||||
className:
|
||||
'bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-110 dark:text-chalkboard-10 rounded-sm border-chalkboard-20/50 dark:border-chalkboard-80/50',
|
||||
success: {
|
||||
iconTheme: {
|
||||
primary: 'oklch(89% 0.16 143.4deg)',
|
||||
secondary: 'oklch(48.62% 0.1654 142.5deg)',
|
||||
<AppStreamProvider>
|
||||
<Router />
|
||||
<Toaster
|
||||
position="bottom-center"
|
||||
toastOptions={{
|
||||
style: {
|
||||
borderRadius: '3px',
|
||||
},
|
||||
duration:
|
||||
window?.localStorage.getItem('playwright') === 'true'
|
||||
? 10 // speed up e2e tests
|
||||
: 1500,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ModalContainer />
|
||||
className:
|
||||
'bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-110 dark:text-chalkboard-10 rounded-sm border-chalkboard-20/50 dark:border-chalkboard-80/50',
|
||||
success: {
|
||||
iconTheme: {
|
||||
primary: 'oklch(89% 0.16 143.4deg)',
|
||||
secondary: 'oklch(48.62% 0.1654 142.5deg)',
|
||||
},
|
||||
duration:
|
||||
window?.localStorage.getItem('playwright') === 'true'
|
||||
? 10 // speed up e2e tests
|
||||
: 1500,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ModalContainer />
|
||||
</AppStreamProvider>
|
||||
</HotkeysProvider>
|
||||
)
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: codeManager?.code || '',
|
||||
@ -35,6 +37,7 @@ export function KclContextProvider({
|
||||
const [errors, setErrors] = useState<KCLError[]>([])
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
useEffect(() => {
|
||||
codeManager.registerCallBacks({
|
||||
@ -50,6 +53,28 @@ export function KclContextProvider({
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Add format code to command palette.
|
||||
useEffect(() => {
|
||||
const commands: Command[] = [
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
description: 'Nicely formats the KCL code in the editor.',
|
||||
needsReview: false,
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: (data) => {
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
]
|
||||
commandBarSend({ type: 'Add commands', data: { commands } })
|
||||
|
||||
return () => {
|
||||
commandBarSend({ type: 'Remove commands', data: { commands } })
|
||||
}
|
||||
}, [kclManager, commandBarSend])
|
||||
|
||||
return (
|
||||
<KclContext.Provider
|
||||
value={{
|
||||
|
||||
@ -304,7 +304,6 @@ describe('testing sketchOnExtrudedFace', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const segmentSnippet = `line([9.7, 9.19], %)`
|
||||
const segmentRange: [number, number] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
@ -321,8 +320,7 @@ describe('testing sketchOnExtrudedFace', () => {
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
ast,
|
||||
segmentPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory
|
||||
extrudePathToNode
|
||||
)
|
||||
if (err(extruded)) throw extruded
|
||||
const { modifiedAst } = extruded
|
||||
@ -345,7 +343,6 @@ const sketch001 = startSketchOn(part001, seg01)`)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const segmentSnippet = `close(%)`
|
||||
const segmentRange: [number, number] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
@ -362,8 +359,7 @@ const sketch001 = startSketchOn(part001, seg01)`)
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
ast,
|
||||
segmentPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory
|
||||
extrudePathToNode
|
||||
)
|
||||
if (err(extruded)) throw extruded
|
||||
const { modifiedAst } = extruded
|
||||
@ -386,7 +382,6 @@ const sketch001 = startSketchOn(part001, seg01)`)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
|
||||
const sketchRange: [number, number] = [
|
||||
code.indexOf(sketchSnippet),
|
||||
@ -404,7 +399,6 @@ const sketch001 = startSketchOn(part001, seg01)`)
|
||||
ast,
|
||||
sketchPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory,
|
||||
'end'
|
||||
)
|
||||
if (err(extruded)) throw extruded
|
||||
@ -436,7 +430,6 @@ const sketch001 = startSketchOn(part001, 'END')`)
|
||||
const part001 = extrude(5 + 7, sketch001)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const segmentSnippet = `line([4.99, -0.46], %)`
|
||||
const segmentRange: [number, number] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
@ -453,8 +446,7 @@ const sketch001 = startSketchOn(part001, 'END')`)
|
||||
const updatedAst = sketchOnExtrudedFace(
|
||||
ast,
|
||||
segmentPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory
|
||||
extrudePathToNode
|
||||
)
|
||||
if (err(updatedAst)) throw updatedAst
|
||||
const newCode = recast(updatedAst.modifiedAst)
|
||||
|
||||
@ -349,7 +349,6 @@ export function sketchOnExtrudedFace(
|
||||
node: Program,
|
||||
sketchPathToNode: PathToNode,
|
||||
extrudePathToNode: PathToNode,
|
||||
programMemory: ProgramMemory,
|
||||
cap: 'none' | 'start' | 'end' = 'none'
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } | Error {
|
||||
let _node = { ...node }
|
||||
@ -388,7 +387,6 @@ export function sketchOnExtrudedFace(
|
||||
if (cap === 'none') {
|
||||
const __tag = addTagForSketchOnFace(
|
||||
{
|
||||
previousProgramMemory: programMemory,
|
||||
pathToNode: sketchPathToNode,
|
||||
node: _node,
|
||||
},
|
||||
|
||||
315
src/lang/modifyAst/addFillet.test.ts
Normal file
@ -0,0 +1,315 @@
|
||||
import {
|
||||
parse,
|
||||
recast,
|
||||
initPromise,
|
||||
PathToNode,
|
||||
Value,
|
||||
Program,
|
||||
CallExpression,
|
||||
} from '../wasm'
|
||||
import { addFillet, isTagUsedInFillet } from './addFillet'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||
import { createLiteral } from 'lang/modifyAst'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise // Initialize the WASM environment before running tests
|
||||
})
|
||||
|
||||
const runFilletTest = async (
|
||||
code: string,
|
||||
segmentSnippet: string,
|
||||
extrudeSnippet: string,
|
||||
radius = createLiteral(5) as Value,
|
||||
expectedCode: string
|
||||
) => {
|
||||
const astOrError = parse(code)
|
||||
if (astOrError instanceof Error) {
|
||||
return new Error('AST not found')
|
||||
}
|
||||
|
||||
const ast = astOrError as Program
|
||||
|
||||
const segmentRange: [number, number] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
]
|
||||
const pathToSegmentNode: PathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
segmentRange
|
||||
)
|
||||
|
||||
const extrudeRange: [number, number] = [
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
]
|
||||
|
||||
const pathToExtrudeNode: PathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
extrudeRange
|
||||
)
|
||||
if (pathToExtrudeNode instanceof Error) {
|
||||
return new Error('Path to extrude node not found')
|
||||
}
|
||||
|
||||
// const radius = createLiteral(5) as Value
|
||||
|
||||
const result = addFillet(ast, pathToSegmentNode, pathToExtrudeNode, radius)
|
||||
if (result instanceof Error) {
|
||||
return result
|
||||
}
|
||||
const { modifiedAst } = result
|
||||
const newCode = recast(modifiedAst)
|
||||
|
||||
expect(newCode).toContain(expectedCode)
|
||||
}
|
||||
|
||||
describe('Testing addFillet', () => {
|
||||
/**
|
||||
* 1. Ideal Case
|
||||
*/
|
||||
|
||||
it('should add a fillet to a specific segment after extrusion, clean', async () => {
|
||||
const code = `
|
||||
const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
const segmentSnippet = `line([60.04, -55.72], %)`
|
||||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
|
||||
const radius = createLiteral(5) as Value
|
||||
const expectedCode = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %, $seg01)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 5, tags: [seg01] }, %)`
|
||||
|
||||
await runFilletTest(
|
||||
code,
|
||||
segmentSnippet,
|
||||
extrudeSnippet,
|
||||
radius,
|
||||
expectedCode
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 2. Case of existing tag in the other line
|
||||
*/
|
||||
|
||||
it('should add a fillet to a specific segment after extrusion with existing tag in any other line', async () => {
|
||||
const code = `
|
||||
const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg01)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
const segmentSnippet = `line([60.04, -55.72], %)`
|
||||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
|
||||
const radius = createLiteral(5) as Value
|
||||
const expectedCode = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %, $seg02)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg01)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 5, tags: [seg02] }, %)`
|
||||
|
||||
await runFilletTest(
|
||||
code,
|
||||
segmentSnippet,
|
||||
extrudeSnippet,
|
||||
radius,
|
||||
expectedCode
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 3. Case of existing tag in the fillet line
|
||||
*/
|
||||
|
||||
it('should add a fillet to a specific segment after extrusion with existing tag in that exact line', async () => {
|
||||
const code = `
|
||||
const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg03)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
const segmentSnippet = `line([-87.24, -47.08], %, $seg03)`
|
||||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
|
||||
const radius = createLiteral(5) as Value
|
||||
const expectedCode = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg03)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 5, tags: [seg03] }, %)`
|
||||
|
||||
await runFilletTest(
|
||||
code,
|
||||
segmentSnippet,
|
||||
extrudeSnippet,
|
||||
radius,
|
||||
expectedCode
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 4. Case of existing fillet on some other segment
|
||||
*/
|
||||
|
||||
it('should add another fillet after the existing fillet', async () => {
|
||||
const code = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg03)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 10, tags: [seg03] }, %)`
|
||||
const segmentSnippet = `line([60.04, -55.72], %)`
|
||||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
|
||||
const radius = createLiteral(5) as Value
|
||||
const expectedCode = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %, $seg01)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg03)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 10, tags: [seg03] }, %)
|
||||
|> fillet({ radius: 5, tags: [seg01] }, %)`
|
||||
|
||||
await runFilletTest(
|
||||
code,
|
||||
segmentSnippet,
|
||||
extrudeSnippet,
|
||||
radius,
|
||||
expectedCode
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Testing isTagUsedInFillet', () => {
|
||||
const code = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.72, 4.13], %)
|
||||
|> line([7.11, 3.48], %, $seg01)
|
||||
|> line([-3.29, -13.85], %)
|
||||
|> line([-6.37, 3.88], %, $seg02)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(-5, sketch001)
|
||||
|> fillet({
|
||||
radius: 1.11,
|
||||
tags: [
|
||||
getOppositeEdge(seg01, %),
|
||||
seg01,
|
||||
getPreviousAdjacentEdge(seg02, %)
|
||||
]
|
||||
}, %)
|
||||
`
|
||||
it('should correctly identify getOppositeEdge and baseEdge edges', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return
|
||||
const lineOfInterest = `line([7.11, 3.48], %, $seg01)`
|
||||
const range: [number, number] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(callExp)) return
|
||||
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
|
||||
expect(edges).toEqual(['getOppositeEdge', 'baseEdge'])
|
||||
})
|
||||
it('should correctly identify getPreviousAdjacentEdge edges', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return
|
||||
const lineOfInterest = `line([-6.37, 3.88], %, $seg02)`
|
||||
const range: [number, number] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(callExp)) return
|
||||
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
|
||||
expect(edges).toEqual(['getPreviousAdjacentEdge'])
|
||||
})
|
||||
it('should correctly identify no edges', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return
|
||||
const lineOfInterest = `line([-3.29, -13.85], %)`
|
||||
const range: [number, number] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(callExp)) return
|
||||
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
|
||||
expect(edges).toEqual([])
|
||||
})
|
||||
})
|
||||
405
src/lang/modifyAst/addFillet.ts
Normal file
@ -0,0 +1,405 @@
|
||||
import {
|
||||
ArrayExpression,
|
||||
CallExpression,
|
||||
ObjectExpression,
|
||||
PathToNode,
|
||||
Program,
|
||||
Value,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
} from '../wasm'
|
||||
import {
|
||||
createCallExpressionStdLib,
|
||||
createLiteral,
|
||||
createPipeSubstitution,
|
||||
createObjectExpression,
|
||||
createArrayExpression,
|
||||
createIdentifier,
|
||||
createPipeExpression,
|
||||
} from '../modifyAst'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
getNodePathFromSourceRange,
|
||||
hasSketchPipeBeenExtruded,
|
||||
traverse,
|
||||
} from '../queryAst'
|
||||
import {
|
||||
addTagForSketchOnFace,
|
||||
getTagFromCallExpression,
|
||||
sketchLineHelperMap,
|
||||
} from '../std/sketch'
|
||||
import { err } from 'lib/trap'
|
||||
import { Selections, canFilletSelection } from 'lib/selections'
|
||||
// import { forEach } from 'jszip'
|
||||
|
||||
export function addFillet(
|
||||
node: Program,
|
||||
pathToSegmentNode: PathToNode,
|
||||
pathToExtrudeNode: PathToNode,
|
||||
radius = createLiteral(5) as Value
|
||||
// shouldPipe = false, // TODO: Implement this feature
|
||||
): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error {
|
||||
// close ast to make mutations safe
|
||||
let _node: Program = JSON.parse(JSON.stringify(node))
|
||||
|
||||
/**
|
||||
* Add Tag to the Segment Expression
|
||||
*/
|
||||
|
||||
// Find the specific sketch segment to tag with the new tag
|
||||
const sketchSegmentChunk = getNodeFromPath(
|
||||
_node,
|
||||
pathToSegmentNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(sketchSegmentChunk)) return sketchSegmentChunk
|
||||
const { node: sketchSegmentNode } = sketchSegmentChunk as {
|
||||
node: CallExpression
|
||||
}
|
||||
|
||||
// Check whether selection is a valid segment from sketchLineHelpersMap
|
||||
if (!(sketchSegmentNode.callee.name in sketchLineHelperMap)) {
|
||||
return new Error('Selection is not a sketch segment')
|
||||
}
|
||||
|
||||
// Add tag to the sketch segment or use existing tag
|
||||
const taggedSegment = addTagForSketchOnFace(
|
||||
{
|
||||
// previousProgramMemory: programMemory,
|
||||
pathToNode: pathToSegmentNode,
|
||||
node: _node,
|
||||
},
|
||||
sketchSegmentNode.callee.name
|
||||
)
|
||||
if (err(taggedSegment)) return taggedSegment
|
||||
const { tag } = taggedSegment
|
||||
|
||||
/**
|
||||
* Find Extrude Expression automatically
|
||||
*/
|
||||
|
||||
// 1. Get the sketch name
|
||||
|
||||
/**
|
||||
* Add Fillet to the Extrude expression
|
||||
*/
|
||||
|
||||
// Create the fillet call expression in one line
|
||||
const filletCall = createCallExpressionStdLib('fillet', [
|
||||
createObjectExpression({
|
||||
radius: radius,
|
||||
tags: createArrayExpression([createIdentifier(tag)]),
|
||||
}),
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
|
||||
// Locate the extrude call
|
||||
const extrudeChunk = getNodeFromPath<VariableDeclaration>(
|
||||
_node,
|
||||
pathToExtrudeNode,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(extrudeChunk)) return extrudeChunk
|
||||
const { node: extrudeVarDecl } = extrudeChunk
|
||||
|
||||
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
||||
const extrudeInit = extrudeDeclarator.init
|
||||
|
||||
if (
|
||||
!extrudeDeclarator ||
|
||||
(extrudeInit.type !== 'CallExpression' &&
|
||||
extrudeInit.type !== 'PipeExpression')
|
||||
) {
|
||||
return new Error('Extrude PipeExpression / CallExpression not found.')
|
||||
}
|
||||
|
||||
// determine if extrude is in a PipeExpression or CallExpression
|
||||
|
||||
// CallExpression - no fillet
|
||||
// PipeExpression - fillet exists
|
||||
|
||||
const getPathToNodeOfFilletLiteral = (
|
||||
pathToExtrudeNode: PathToNode,
|
||||
extrudeDeclarator: VariableDeclarator,
|
||||
tag: string
|
||||
): PathToNode => {
|
||||
let pathToFilletObj: any
|
||||
let inFillet = false
|
||||
traverse(extrudeDeclarator.init, {
|
||||
enter(node, path) {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||
inFillet = true
|
||||
}
|
||||
if (inFillet && node.type === 'ObjectExpression') {
|
||||
const hasTag = node.properties.some((prop) => {
|
||||
const isTagProp = prop.key.name === 'tags'
|
||||
if (isTagProp && prop.value.type === 'ArrayExpression') {
|
||||
return prop.value.elements.some(
|
||||
(element) =>
|
||||
element.type === 'Identifier' && element.name === tag
|
||||
)
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (!hasTag) return false
|
||||
pathToFilletObj = path
|
||||
node.properties.forEach((prop, index) => {
|
||||
if (prop.key.name === 'radius') {
|
||||
pathToFilletObj.push(
|
||||
['properties', 'ObjectExpression'],
|
||||
[index, 'index'],
|
||||
['value', 'Property']
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
leave(node) {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||
inFillet = false
|
||||
}
|
||||
},
|
||||
})
|
||||
let indexOfPipeExpression = pathToExtrudeNode.findIndex(
|
||||
(path) => path[1] === 'PipeExpression'
|
||||
)
|
||||
indexOfPipeExpression =
|
||||
indexOfPipeExpression === -1
|
||||
? pathToExtrudeNode.length
|
||||
: indexOfPipeExpression
|
||||
|
||||
return [
|
||||
...pathToExtrudeNode.slice(0, indexOfPipeExpression),
|
||||
...pathToFilletObj,
|
||||
]
|
||||
}
|
||||
|
||||
if (extrudeInit.type === 'CallExpression') {
|
||||
// 1. no fillet case
|
||||
extrudeDeclarator.init = createPipeExpression([extrudeInit, filletCall])
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToFilletNode: getPathToNodeOfFilletLiteral(
|
||||
pathToExtrudeNode,
|
||||
extrudeDeclarator,
|
||||
tag
|
||||
),
|
||||
}
|
||||
} else if (extrudeInit.type === 'PipeExpression') {
|
||||
// 2. fillet case
|
||||
|
||||
// there are 2 options here:
|
||||
|
||||
const existingFilletCall = extrudeInit.body.find((node) => {
|
||||
return node.type === 'CallExpression' && node.callee.name === 'fillet'
|
||||
})
|
||||
|
||||
if (!existingFilletCall || existingFilletCall.type !== 'CallExpression') {
|
||||
return new Error('Fillet CallExpression not found.')
|
||||
}
|
||||
|
||||
// check if the existing fillet has the same tag as the new fillet
|
||||
let filletTag = null
|
||||
if (existingFilletCall.arguments[0].type === 'ObjectExpression') {
|
||||
const properties = (existingFilletCall.arguments[0] as ObjectExpression)
|
||||
.properties
|
||||
const tagsProperty = properties.find((prop) => prop.key.name === 'tags')
|
||||
if (tagsProperty && tagsProperty.value.type === 'ArrayExpression') {
|
||||
const elements = (tagsProperty.value as ArrayExpression).elements
|
||||
if (elements.length > 0 && elements[0].type === 'Identifier') {
|
||||
filletTag = elements[0].name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return new Error('Expected an ObjectExpression node')
|
||||
}
|
||||
|
||||
if (filletTag !== tag) {
|
||||
extrudeInit.body.push(filletCall)
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToFilletNode: getPathToNodeOfFilletLiteral(
|
||||
pathToExtrudeNode,
|
||||
extrudeDeclarator,
|
||||
tag
|
||||
),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return new Error('Unsupported extrude type.')
|
||||
}
|
||||
|
||||
return new Error('Unsupported extrude type.')
|
||||
}
|
||||
|
||||
export const hasValidFilletSelection = ({
|
||||
selectionRanges,
|
||||
ast,
|
||||
code,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
ast: Program
|
||||
code: string
|
||||
}) => {
|
||||
// case 0: check if there is anything filletable in the scene
|
||||
let extrudeExists = false
|
||||
traverse(ast, {
|
||||
enter(node) {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'extrude') {
|
||||
extrudeExists = true
|
||||
}
|
||||
},
|
||||
})
|
||||
if (!extrudeExists) return false
|
||||
|
||||
// case 1: nothing selected, test whether the extrusion exists
|
||||
if (selectionRanges) {
|
||||
if (selectionRanges.codeBasedSelections.length === 0) {
|
||||
return true
|
||||
}
|
||||
const range0 = selectionRanges.codeBasedSelections[0].range[0]
|
||||
const codeLength = code.length
|
||||
if (range0 === codeLength) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// case 2: sketch segment selected, test whether it is extruded
|
||||
// TODO: add loft / sweep check
|
||||
if (selectionRanges.codeBasedSelections.length > 0) {
|
||||
const isExtruded = hasSketchPipeBeenExtruded(
|
||||
selectionRanges.codeBasedSelections[0],
|
||||
ast
|
||||
)
|
||||
if (isExtruded) {
|
||||
const pathToSelectedNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
selectionRanges.codeBasedSelections[0].range
|
||||
)
|
||||
const segmentNode = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToSelectedNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(segmentNode)) return false
|
||||
if (segmentNode.node.type === 'CallExpression') {
|
||||
const segmentName = segmentNode.node.callee.name
|
||||
if (segmentName in sketchLineHelperMap) {
|
||||
const edges = isTagUsedInFillet({
|
||||
ast,
|
||||
callExp: segmentNode.node,
|
||||
})
|
||||
// edge has already been filleted
|
||||
if (
|
||||
['edge', 'default'].includes(
|
||||
selectionRanges.codeBasedSelections[0].type
|
||||
) &&
|
||||
edges.includes('baseEdge')
|
||||
)
|
||||
return false
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return canFilletSelection(selectionRanges)
|
||||
}
|
||||
|
||||
type EdgeTypes =
|
||||
| 'baseEdge'
|
||||
| 'getNextAdjacentEdge'
|
||||
| 'getPreviousAdjacentEdge'
|
||||
| 'getOppositeEdge'
|
||||
|
||||
export const isTagUsedInFillet = ({
|
||||
ast,
|
||||
callExp,
|
||||
}: {
|
||||
ast: Program
|
||||
callExp: CallExpression
|
||||
}): Array<EdgeTypes> => {
|
||||
const tag = getTagFromCallExpression(callExp)
|
||||
if (err(tag)) return []
|
||||
|
||||
let inFillet = false
|
||||
let inObj = false
|
||||
let inTagHelper: EdgeTypes | '' = ''
|
||||
const edges: Array<EdgeTypes> = []
|
||||
traverse(ast, {
|
||||
enter: (node) => {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||
inFillet = true
|
||||
}
|
||||
if (inFillet && node.type === 'ObjectExpression') {
|
||||
node.properties.forEach((prop) => {
|
||||
if (
|
||||
prop.key.name === 'tags' &&
|
||||
prop.value.type === 'ArrayExpression'
|
||||
) {
|
||||
inObj = true
|
||||
}
|
||||
})
|
||||
}
|
||||
if (
|
||||
inObj &&
|
||||
inFillet &&
|
||||
node.type === 'CallExpression' &&
|
||||
(node.callee.name === 'getOppositeEdge' ||
|
||||
node.callee.name === 'getNextAdjacentEdge' ||
|
||||
node.callee.name === 'getPreviousAdjacentEdge')
|
||||
) {
|
||||
inTagHelper = node.callee.name
|
||||
}
|
||||
if (
|
||||
inObj &&
|
||||
inFillet &&
|
||||
!inTagHelper &&
|
||||
node.type === 'Identifier' &&
|
||||
node.name === tag
|
||||
) {
|
||||
edges.push('baseEdge')
|
||||
}
|
||||
if (
|
||||
inObj &&
|
||||
inFillet &&
|
||||
inTagHelper &&
|
||||
node.type === 'Identifier' &&
|
||||
node.name === tag
|
||||
) {
|
||||
edges.push(inTagHelper)
|
||||
}
|
||||
},
|
||||
leave: (node) => {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||
inFillet = false
|
||||
}
|
||||
if (inFillet && node.type === 'ObjectExpression') {
|
||||
node.properties.forEach((prop) => {
|
||||
if (
|
||||
prop.key.name === 'tags' &&
|
||||
prop.value.type === 'ArrayExpression'
|
||||
) {
|
||||
inObj = true
|
||||
}
|
||||
})
|
||||
}
|
||||
if (
|
||||
inObj &&
|
||||
inFillet &&
|
||||
node.type === 'CallExpression' &&
|
||||
(node.callee.name === 'getOppositeEdge' ||
|
||||
node.callee.name === 'getNextAdjacentEdge' ||
|
||||
node.callee.name === 'getPreviousAdjacentEdge')
|
||||
) {
|
||||
inTagHelper = ''
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return edges
|
||||
}
|
||||
@ -302,6 +302,30 @@ class EngineConnection extends EventTarget {
|
||||
mediaStream?: MediaStream
|
||||
freezeFrame: boolean = false
|
||||
|
||||
onIceCandidate = function (
|
||||
this: RTCPeerConnection,
|
||||
event: RTCPeerConnectionIceEvent
|
||||
) {}
|
||||
onIceCandidateError = function (
|
||||
this: RTCPeerConnection,
|
||||
event: RTCPeerConnectionIceErrorEvent
|
||||
) {}
|
||||
onConnectionStateChange = function (this: RTCPeerConnection, event: Event) {}
|
||||
onDataChannelOpen = function (this: RTCDataChannel, event: Event) {}
|
||||
onDataChannelClose = function (this: RTCDataChannel, event: Event) {}
|
||||
onDataChannelError = function (this: RTCDataChannel, event: Event) {}
|
||||
onDataChannelMessage = function (this: RTCDataChannel, event: MessageEvent) {}
|
||||
onDataChannel = function (
|
||||
this: RTCPeerConnection,
|
||||
event: RTCDataChannelEvent
|
||||
) {}
|
||||
onTrack = function (this: RTCPeerConnection, event: RTCTrackEvent) {}
|
||||
onWebSocketOpen = function (event: Event) {}
|
||||
onWebSocketClose = function (event: Event) {}
|
||||
onWebSocketError = function (event: Event) {}
|
||||
onWebSocketMessage = function (event: MessageEvent) {}
|
||||
onNetworkStatusReady = () => {}
|
||||
|
||||
private _state: EngineConnectionState = {
|
||||
type: EngineConnectionStateType.Fresh,
|
||||
}
|
||||
@ -346,6 +370,7 @@ class EngineConnection extends EventTarget {
|
||||
private engineCommandManager: EngineCommandManager
|
||||
|
||||
private pingPongSpan: { ping?: Date; pong?: Date }
|
||||
private pingIntervalId: ReturnType<typeof setInterval>
|
||||
|
||||
constructor({
|
||||
engineCommandManager,
|
||||
@ -368,7 +393,7 @@ class EngineConnection extends EventTarget {
|
||||
// Without an interval ping, our connection will timeout.
|
||||
// If this.freezeFrame is true we skip this logic so only reconnect
|
||||
// happens on mouse move
|
||||
setInterval(() => {
|
||||
this.pingIntervalId = setInterval(() => {
|
||||
if (this.freezeFrame) return
|
||||
|
||||
switch (this.state.type as EngineConnectionStateType) {
|
||||
@ -434,6 +459,44 @@ class EngineConnection extends EventTarget {
|
||||
tearDown(opts?: { freeze: boolean }) {
|
||||
this.freezeFrame = opts?.freeze ?? false
|
||||
this.disconnectAll()
|
||||
clearInterval(this.pingIntervalId)
|
||||
|
||||
this.pc?.removeEventListener('icecandidate', this.onIceCandidate)
|
||||
this.pc?.removeEventListener('icecandidateerror', this.onIceCandidateError)
|
||||
this.pc?.removeEventListener(
|
||||
'connectionstatechange',
|
||||
this.onConnectionStateChange
|
||||
)
|
||||
this.pc?.removeEventListener('track', this.onTrack)
|
||||
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'open',
|
||||
this.onDataChannelOpen
|
||||
)
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'close',
|
||||
this.onDataChannelClose
|
||||
)
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'error',
|
||||
this.onDataChannelError
|
||||
)
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'message',
|
||||
this.onDataChannelMessage
|
||||
)
|
||||
this.pc?.removeEventListener('datachannel', this.onDataChannel)
|
||||
|
||||
this.websocket?.removeEventListener('open', this.onWebSocketOpen)
|
||||
this.websocket?.removeEventListener('close', this.onWebSocketClose)
|
||||
this.websocket?.removeEventListener('error', this.onWebSocketError)
|
||||
this.websocket?.removeEventListener('message', this.onWebSocketMessage)
|
||||
|
||||
window.removeEventListener(
|
||||
'use-network-status-ready',
|
||||
this.onNetworkStatusReady
|
||||
)
|
||||
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Disconnecting,
|
||||
value: { type: DisconnectingType.Quit },
|
||||
@ -477,7 +540,7 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
}
|
||||
|
||||
this.pc.addEventListener('icecandidate', (event) => {
|
||||
this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => {
|
||||
if (event.candidate === null) {
|
||||
return
|
||||
}
|
||||
@ -499,18 +562,20 @@ class EngineConnection extends EventTarget {
|
||||
usernameFragment: event.candidate.usernameFragment || undefined,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('icecandidate', this.onIceCandidate)
|
||||
|
||||
this.pc.addEventListener('icecandidateerror', (_event: Event) => {
|
||||
this.onIceCandidateError = (_event: Event) => {
|
||||
const event = _event as RTCPeerConnectionIceErrorEvent
|
||||
console.warn(
|
||||
`ICE candidate returned an error: ${event.errorCode}: ${event.errorText} for ${event.url}`
|
||||
)
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('icecandidateerror', this.onIceCandidateError)
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionstatechange_event
|
||||
// Event type: generic Event type...
|
||||
this.pc.addEventListener('connectionstatechange', (event: any) => {
|
||||
this.onConnectionStateChange = (event: any) => {
|
||||
console.log('connectionstatechange: ' + event.target?.connectionState)
|
||||
switch (event.target?.connectionState) {
|
||||
// From what I understand, only after have we done the ICE song and
|
||||
@ -539,9 +604,13 @@ class EngineConnection extends EventTarget {
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener(
|
||||
'connectionstatechange',
|
||||
this.onConnectionStateChange
|
||||
)
|
||||
|
||||
this.pc.addEventListener('track', (event) => {
|
||||
this.onTrack = (event) => {
|
||||
const mediaStream = event.streams[0]
|
||||
|
||||
this.state = {
|
||||
@ -625,9 +694,10 @@ class EngineConnection extends EventTarget {
|
||||
// to pass it to the rest of the application.
|
||||
|
||||
this.mediaStream = mediaStream
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('track', this.onTrack)
|
||||
|
||||
this.pc.addEventListener('datachannel', (event) => {
|
||||
this.onDataChannel = (event) => {
|
||||
this.unreliableDataChannel = event.channel
|
||||
|
||||
this.state = {
|
||||
@ -638,7 +708,7 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
}
|
||||
|
||||
this.unreliableDataChannel.addEventListener('open', (event) => {
|
||||
this.onDataChannelOpen = (event) => {
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: {
|
||||
@ -654,14 +724,22 @@ class EngineConnection extends EventTarget {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EngineConnectionEvents.Opened, { detail: this })
|
||||
)
|
||||
})
|
||||
}
|
||||
this.unreliableDataChannel?.addEventListener(
|
||||
'open',
|
||||
this.onDataChannelOpen
|
||||
)
|
||||
|
||||
this.unreliableDataChannel.addEventListener('close', (event) => {
|
||||
this.onDataChannelClose = (event) => {
|
||||
this.disconnectAll()
|
||||
this.finalizeIfAllConnectionsClosed()
|
||||
})
|
||||
}
|
||||
this.unreliableDataChannel?.addEventListener(
|
||||
'close',
|
||||
this.onDataChannelClose
|
||||
)
|
||||
|
||||
this.unreliableDataChannel.addEventListener('error', (event) => {
|
||||
this.onDataChannelError = (event) => {
|
||||
this.disconnectAll()
|
||||
|
||||
this.state = {
|
||||
@ -674,8 +752,13 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
this.unreliableDataChannel.addEventListener('message', (event) => {
|
||||
}
|
||||
this.unreliableDataChannel?.addEventListener(
|
||||
'error',
|
||||
this.onDataChannelError
|
||||
)
|
||||
|
||||
this.onDataChannelMessage = (event) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.engineCommandManager.unreliableSubscriptions[result.type] || {}
|
||||
@ -697,8 +780,13 @@ class EngineConnection extends EventTarget {
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
this.unreliableDataChannel.addEventListener(
|
||||
'message',
|
||||
this.onDataChannelMessage
|
||||
)
|
||||
}
|
||||
this.pc.addEventListener('datachannel', this.onDataChannel)
|
||||
}
|
||||
|
||||
const createWebSocketConnection = () => {
|
||||
@ -712,7 +800,7 @@ class EngineConnection extends EventTarget {
|
||||
this.websocket = new WebSocket(this.url, [])
|
||||
this.websocket.binaryType = 'arraybuffer'
|
||||
|
||||
this.websocket.addEventListener('open', (event) => {
|
||||
this.onWebSocketOpen = (event) => {
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: {
|
||||
@ -733,14 +821,16 @@ class EngineConnection extends EventTarget {
|
||||
// Send an initial ping
|
||||
this.send({ type: 'ping' })
|
||||
this.pingPongSpan.ping = new Date()
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
||||
|
||||
this.websocket.addEventListener('close', (event) => {
|
||||
this.onWebSocketClose = (event) => {
|
||||
this.disconnectAll()
|
||||
this.finalizeIfAllConnectionsClosed()
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('close', this.onWebSocketClose)
|
||||
|
||||
this.websocket.addEventListener('error', (event) => {
|
||||
this.onWebSocketError = (event) => {
|
||||
this.disconnectAll()
|
||||
|
||||
this.state = {
|
||||
@ -753,9 +843,10 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('error', this.onWebSocketError)
|
||||
|
||||
this.websocket.addEventListener('message', (event) => {
|
||||
this.onWebSocketMessage = (event) => {
|
||||
// In the EngineConnection, we're looking for messages to/from
|
||||
// the server that relate to the ICE handshake, or WebRTC
|
||||
// negotiation. There may be other messages (including ArrayBuffer
|
||||
@ -960,15 +1051,20 @@ class EngineConnection extends EventTarget {
|
||||
})
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('message', this.onWebSocketMessage)
|
||||
}
|
||||
|
||||
if (reconnecting) {
|
||||
createWebSocketConnection()
|
||||
} else {
|
||||
window.addEventListener('use-network-status-ready', () => {
|
||||
this.onNetworkStatusReady = () => {
|
||||
createWebSocketConnection()
|
||||
})
|
||||
}
|
||||
window.addEventListener(
|
||||
'use-network-status-ready',
|
||||
this.onNetworkStatusReady
|
||||
)
|
||||
}
|
||||
}
|
||||
// Do not change this back to an object or any, we should only be sending the
|
||||
@ -1154,7 +1250,15 @@ export class EngineCommandManager extends EventTarget {
|
||||
private makeDefaultPlanes: () => Promise<DefaultPlanes> | null = () => null
|
||||
private modifyGrid: (hidden: boolean) => Promise<void> | null = () => null
|
||||
|
||||
private onEngineConnectionOpened = () => {}
|
||||
private onEngineConnectionClosed = () => {}
|
||||
private onEngineConnectionStarted = ({ detail: engineConnection }: any) => {}
|
||||
private onEngineConnectionNewTrack = ({
|
||||
detail,
|
||||
}: CustomEvent<NewTrackArgs>) => {}
|
||||
|
||||
start({
|
||||
restart,
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
width,
|
||||
@ -1170,6 +1274,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
showScaleGrid: false,
|
||||
},
|
||||
}: {
|
||||
restart?: boolean
|
||||
setMediaStream: (stream: MediaStream) => void
|
||||
setIsStreamReady: (isStreamReady: boolean) => void
|
||||
width: number
|
||||
@ -1215,162 +1320,168 @@ export class EngineCommandManager extends EventTarget {
|
||||
})
|
||||
)
|
||||
|
||||
this.onEngineConnectionOpened = () => {
|
||||
// Set the stream background color
|
||||
// This takes RGBA values from 0-1
|
||||
// So we convert from the conventional 0-255 found in Figma
|
||||
|
||||
void this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'set_background_color',
|
||||
color: getThemeColorForEngine(settings.theme),
|
||||
},
|
||||
})
|
||||
|
||||
// Sets the default line colors
|
||||
const opposingTheme = getOppositeTheme(settings.theme)
|
||||
this.sendSceneCommand({
|
||||
cmd_id: uuidv4(),
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'set_default_system_properties',
|
||||
color: getThemeColorForEngine(opposingTheme),
|
||||
},
|
||||
})
|
||||
|
||||
// Set the edge lines visibility
|
||||
this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'edge_lines_visible' as any, // TODO: update kittycad.ts to use the correct type
|
||||
hidden: !settings.highlightEdges,
|
||||
},
|
||||
})
|
||||
|
||||
this._camControlsCameraChange()
|
||||
this.sendSceneCommand({
|
||||
// CameraControls subscribes to default_camera_get_settings response events
|
||||
// firing this at connection ensure the camera's are synced initially
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
// We want modify the grid first because we don't want it to flash.
|
||||
// Ideally these would already be default hidden in engine (TODO do
|
||||
// that) https://github.com/KittyCAD/engine/issues/2282
|
||||
this.modifyGrid(!settings.showScaleGrid)?.then(async () => {
|
||||
await this.initPlanes()
|
||||
this.resolveReady()
|
||||
setIsStreamReady(true)
|
||||
await executeCode()
|
||||
})
|
||||
}
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.Opened,
|
||||
() => {
|
||||
// Set the stream background color
|
||||
// This takes RGBA values from 0-1
|
||||
// So we convert from the conventional 0-255 found in Figma
|
||||
|
||||
void this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'set_background_color',
|
||||
color: getThemeColorForEngine(settings.theme),
|
||||
},
|
||||
})
|
||||
|
||||
// Sets the default line colors
|
||||
const opposingTheme = getOppositeTheme(settings.theme)
|
||||
this.sendSceneCommand({
|
||||
cmd_id: uuidv4(),
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'set_default_system_properties',
|
||||
color: getThemeColorForEngine(opposingTheme),
|
||||
},
|
||||
})
|
||||
|
||||
// Set the edge lines visibility
|
||||
this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'edge_lines_visible' as any, // TODO: update kittycad.ts to use the correct type
|
||||
hidden: !settings.highlightEdges,
|
||||
},
|
||||
})
|
||||
|
||||
this._camControlsCameraChange()
|
||||
this.sendSceneCommand({
|
||||
// CameraControls subscribes to default_camera_get_settings response events
|
||||
// firing this at connection ensure the camera's are synced initially
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
// We want modify the grid first because we don't want it to flash.
|
||||
// Ideally these would already be default hidden in engine (TODO do
|
||||
// that) https://github.com/KittyCAD/engine/issues/2282
|
||||
this.modifyGrid(!settings.showScaleGrid)?.then(async () => {
|
||||
await this.initPlanes()
|
||||
this.resolveReady()
|
||||
setIsStreamReady(true)
|
||||
await executeCode()
|
||||
})
|
||||
}
|
||||
this.onEngineConnectionOpened
|
||||
)
|
||||
|
||||
this.onEngineConnectionClosed = () => {
|
||||
setIsStreamReady(false)
|
||||
}
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.Closed,
|
||||
() => {
|
||||
setIsStreamReady(false)
|
||||
}
|
||||
this.onEngineConnectionClosed
|
||||
)
|
||||
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.ConnectionStarted,
|
||||
({ detail: engineConnection }: any) => {
|
||||
engineConnection?.pc?.addEventListener(
|
||||
'datachannel',
|
||||
(event: RTCDataChannelEvent) => {
|
||||
let unreliableDataChannel = event.channel
|
||||
this.onEngineConnectionStarted = ({ detail: engineConnection }: any) => {
|
||||
engineConnection?.pc?.addEventListener(
|
||||
'datachannel',
|
||||
(event: RTCDataChannelEvent) => {
|
||||
let unreliableDataChannel = event.channel
|
||||
|
||||
unreliableDataChannel.addEventListener(
|
||||
'message',
|
||||
(event: MessageEvent) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
// per unreliable subscription.
|
||||
(callback) => {
|
||||
let data = result?.data
|
||||
if (isHighlightSetEntity_type(data)) {
|
||||
if (
|
||||
data.sequence !== undefined &&
|
||||
data.sequence > this.inSequence
|
||||
) {
|
||||
this.inSequence = data.sequence
|
||||
callback(result)
|
||||
}
|
||||
unreliableDataChannel.addEventListener(
|
||||
'message',
|
||||
(event: MessageEvent) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
// per unreliable subscription.
|
||||
(callback) => {
|
||||
let data = result?.data
|
||||
if (isHighlightSetEntity_type(data)) {
|
||||
if (
|
||||
data.sequence !== undefined &&
|
||||
data.sequence > this.inSequence
|
||||
) {
|
||||
this.inSequence = data.sequence
|
||||
callback(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// When the EngineConnection starts a connection, we want to register
|
||||
// callbacks into the WebSocket/PeerConnection.
|
||||
engineConnection.websocket?.addEventListener('message', ((
|
||||
event: MessageEvent
|
||||
) => {
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
// If the data is an ArrayBuffer, it's the result of an export command,
|
||||
// because in all other cases we send JSON strings. But in the case of
|
||||
// export we send a binary blob.
|
||||
// Pass this to our export function.
|
||||
exportSave(event.data).then(() => {
|
||||
this.pendingExport?.resolve()
|
||||
}, this.pendingExport?.reject)
|
||||
} else {
|
||||
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
||||
event.data
|
||||
)
|
||||
if (
|
||||
message.success &&
|
||||
(message.resp.type === 'modeling' ||
|
||||
message.resp.type === 'modeling_batch') &&
|
||||
message.request_id
|
||||
) {
|
||||
this.handleModelingCommand(
|
||||
message.resp,
|
||||
message.request_id,
|
||||
message
|
||||
}
|
||||
)
|
||||
} else if (
|
||||
!message.success &&
|
||||
message.request_id &&
|
||||
this.artifactMap[message.request_id]
|
||||
) {
|
||||
this.handleFailedModelingCommand(message.request_id, message)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// When the EngineConnection starts a connection, we want to register
|
||||
// callbacks into the WebSocket/PeerConnection.
|
||||
engineConnection.websocket?.addEventListener('message', ((
|
||||
event: MessageEvent
|
||||
) => {
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
// If the data is an ArrayBuffer, it's the result of an export command,
|
||||
// because in all other cases we send JSON strings. But in the case of
|
||||
// export we send a binary blob.
|
||||
// Pass this to our export function.
|
||||
exportSave(event.data).then(() => {
|
||||
this.pendingExport?.resolve()
|
||||
}, this.pendingExport?.reject)
|
||||
} else {
|
||||
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
||||
event.data
|
||||
)
|
||||
if (
|
||||
message.success &&
|
||||
(message.resp.type === 'modeling' ||
|
||||
message.resp.type === 'modeling_batch') &&
|
||||
message.request_id
|
||||
) {
|
||||
this.handleModelingCommand(
|
||||
message.resp,
|
||||
message.request_id,
|
||||
message
|
||||
)
|
||||
} else if (
|
||||
!message.success &&
|
||||
message.request_id &&
|
||||
this.artifactMap[message.request_id]
|
||||
) {
|
||||
this.handleFailedModelingCommand(message.request_id, message)
|
||||
}
|
||||
}) as EventListener)
|
||||
}
|
||||
}) as EventListener)
|
||||
|
||||
this.engineConnection?.addEventListener(
|
||||
EngineConnectionEvents.NewTrack,
|
||||
(({ detail: { mediaStream } }: CustomEvent<NewTrackArgs>) => {
|
||||
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
||||
console.error(
|
||||
'video track mute: check webrtc internals -> inbound rtp'
|
||||
)
|
||||
})
|
||||
this.onEngineConnectionNewTrack = ({
|
||||
detail: { mediaStream },
|
||||
}: CustomEvent<NewTrackArgs>) => {
|
||||
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
||||
console.error(
|
||||
'video track mute: check webrtc internals -> inbound rtp'
|
||||
)
|
||||
})
|
||||
|
||||
setMediaStream(mediaStream)
|
||||
}) as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.connect()
|
||||
setMediaStream(mediaStream)
|
||||
}
|
||||
this.engineConnection?.addEventListener(
|
||||
EngineConnectionEvents.NewTrack,
|
||||
this.onEngineConnectionNewTrack as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.connect()
|
||||
}
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.ConnectionStarted,
|
||||
this.onEngineConnectionStarted
|
||||
)
|
||||
}
|
||||
|
||||
@ -1629,7 +1740,26 @@ export class EngineCommandManager extends EventTarget {
|
||||
}
|
||||
tearDown() {
|
||||
if (this.engineConnection) {
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.Opened,
|
||||
this.onEngineConnectionOpened
|
||||
)
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.Closed,
|
||||
this.onEngineConnectionClosed
|
||||
)
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.ConnectionStarted,
|
||||
this.onEngineConnectionStarted
|
||||
)
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.NewTrack,
|
||||
this.onEngineConnectionNewTrack as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.tearDown()
|
||||
this.engineConnection = undefined
|
||||
|
||||
// Our window.tearDown assignment causes this case to happen which is
|
||||
// only really for tests.
|
||||
// @ts-ignore
|
||||
|
||||
@ -221,7 +221,7 @@ describe('testing addTagForSketchOnFace', () => {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const sketchOnFaceRetVal = addTagForSketchOnFace(
|
||||
{
|
||||
previousProgramMemory: programMemory,
|
||||
// previousProgramMemory: programMemory, // redundant?
|
||||
pathToNode,
|
||||
node: ast,
|
||||
},
|
||||
|
||||
@ -28,7 +28,6 @@ import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||
|
||||
import {
|
||||
SketchLineHelper,
|
||||
ModifyAstBase,
|
||||
TransformCallback,
|
||||
ConstrainInfo,
|
||||
RawValues,
|
||||
@ -37,6 +36,7 @@ import {
|
||||
SingleValueInput,
|
||||
VarValueKeys,
|
||||
ArrayOrObjItemInput,
|
||||
AddTagInfo,
|
||||
} from 'lang/std/stdTypes'
|
||||
|
||||
import {
|
||||
@ -308,6 +308,18 @@ function singleRawValueHelper(
|
||||
]
|
||||
}
|
||||
|
||||
function getTag(index = 2): SketchLineHelper['getTag'] {
|
||||
return (callExp: CallExpression) => {
|
||||
if (callExp.type !== 'CallExpression')
|
||||
return new Error('Not a CallExpression')
|
||||
const arg = callExp.arguments?.[index]
|
||||
if (!arg) return new Error('No argument')
|
||||
if (arg.type !== 'TagDeclarator')
|
||||
return new Error('Tag not a TagDeclarator')
|
||||
return arg.value
|
||||
}
|
||||
}
|
||||
|
||||
export const lineTo: SketchLineHelper = {
|
||||
add: ({
|
||||
node,
|
||||
@ -377,6 +389,7 @@ export const lineTo: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -503,6 +516,7 @@ export const line: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -563,6 +577,7 @@ export const xLineTo: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
horzVertConstraintInfoHelper(
|
||||
@ -623,6 +638,7 @@ export const yLineTo: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
horzVertConstraintInfoHelper(
|
||||
@ -682,6 +698,7 @@ export const xLine: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
horzVertConstraintInfoHelper(
|
||||
@ -738,6 +755,7 @@ export const yLine: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
horzVertConstraintInfoHelper(
|
||||
@ -830,6 +848,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp: CallExpression, code, pathToNode) => {
|
||||
if (callExp.type !== 'CallExpression') return []
|
||||
@ -948,6 +967,7 @@ export const angledLine: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1044,6 +1064,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1140,6 +1161,7 @@ export const angledLineOfYLength: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1227,6 +1249,7 @@ export const angledLineToX: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1316,6 +1339,7 @@ export const angledLineToY: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1440,6 +1464,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp: CallExpression, code, pathToNode) => {
|
||||
if (callExp.type !== 'CallExpression') return []
|
||||
@ -1792,10 +1817,7 @@ export function replaceSketchLine({
|
||||
return { modifiedAst, valueUsedInTransform, pathToNode }
|
||||
}
|
||||
|
||||
export function addTagForSketchOnFace(
|
||||
a: ModifyAstBase,
|
||||
expressionName: string
|
||||
) {
|
||||
export function addTagForSketchOnFace(a: AddTagInfo, expressionName: string) {
|
||||
if (expressionName === 'close') {
|
||||
return addTag(1)(a)
|
||||
}
|
||||
@ -1806,6 +1828,17 @@ export function addTagForSketchOnFace(
|
||||
return new Error(`"${expressionName}" is not a sketch line helper`)
|
||||
}
|
||||
|
||||
export function getTagFromCallExpression(
|
||||
callExp: CallExpression
|
||||
): string | Error {
|
||||
if (callExp.callee.name === 'close') return getTag(1)(callExp)
|
||||
if (callExp.callee.name in sketchLineHelperMap) {
|
||||
const { getTag } = sketchLineHelperMap[callExp.callee.name]
|
||||
return getTag(callExp)
|
||||
}
|
||||
return new Error(`"${callExp.callee.name}" is not a sketch line helper`)
|
||||
}
|
||||
|
||||
function isAngleLiteral(lineArugement: Value): boolean {
|
||||
return lineArugement?.type === 'ArrayExpression'
|
||||
? isLiteralArrayOrStatic(lineArugement.elements[0])
|
||||
@ -1816,9 +1849,7 @@ function isAngleLiteral(lineArugement: Value): boolean {
|
||||
: false
|
||||
}
|
||||
|
||||
type addTagFn = (
|
||||
a: ModifyAstBase
|
||||
) => { modifiedAst: Program; tag: string } | Error
|
||||
type addTagFn = (a: AddTagInfo) => { modifiedAst: Program; tag: string } | Error
|
||||
|
||||
function addTag(tagIndex = 2): addTagFn {
|
||||
return ({ node, pathToNode }) => {
|
||||
|
||||
@ -32,6 +32,11 @@ export interface ModifyAstBase {
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
||||
export interface AddTagInfo {
|
||||
node: Program
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
||||
interface addCall extends ModifyAstBase {
|
||||
to: [number, number]
|
||||
from: [number, number]
|
||||
@ -127,7 +132,8 @@ export interface SketchLineHelper {
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
| Error
|
||||
addTag: (a: ModifyAstBase) =>
|
||||
getTag: (a: CallExpression) => string | Error
|
||||
addTag: (a: AddTagInfo) =>
|
||||
| {
|
||||
modifiedAst: Program
|
||||
tag: string
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { CommandSetConfig } from 'lib/commandTypes'
|
||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||
import { authMachine } from 'machines/authMachine'
|
||||
|
||||
type AuthCommandSchema = {}
|
||||
|
||||
export const authCommandBarConfig: CommandSetConfig<
|
||||
export const authCommandBarConfig: StateMachineCommandSetConfig<
|
||||
typeof authMachine,
|
||||
AuthCommandSchema
|
||||
> = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { CommandSetConfig } from 'lib/commandTypes'
|
||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||
import { homeMachine } from 'machines/homeMachine'
|
||||
|
||||
export type HomeCommandSchema = {
|
||||
@ -17,7 +17,7 @@ export type HomeCommandSchema = {
|
||||
}
|
||||
}
|
||||
|
||||
export const homeCommandBarConfig: CommandSetConfig<
|
||||
export const homeCommandBarConfig: StateMachineCommandSetConfig<
|
||||
typeof homeMachine,
|
||||
HomeCommandSchema
|
||||
> = {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { CommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||
|
||||
type OutputFormat = Models['OutputFormat_type']
|
||||
type OutputTypeKey = OutputFormat['type']
|
||||
@ -27,9 +27,17 @@ export type ModelingCommandSchema = {
|
||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||
distance: KclCommandValue
|
||||
}
|
||||
Fillet: {
|
||||
// todo
|
||||
selection: Selections
|
||||
radius: KclCommandValue
|
||||
}
|
||||
'change tool': {
|
||||
tool: SketchTool
|
||||
}
|
||||
}
|
||||
|
||||
export const modelingMachineConfig: CommandSetConfig<
|
||||
export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
typeof modelingMachine,
|
||||
ModelingCommandSchema
|
||||
> = {
|
||||
@ -37,22 +45,47 @@ export const modelingMachineConfig: CommandSetConfig<
|
||||
description: 'Enter sketch mode.',
|
||||
icon: 'sketch',
|
||||
},
|
||||
// TODO the event is no 'change tool' with data: 'line', 'rectangle' etc
|
||||
// 'Equip Line tool': {
|
||||
// description: 'Start drawing straight lines.',
|
||||
// icon: 'line',
|
||||
// displayName: 'Line',
|
||||
// },
|
||||
// 'Equip tangential arc to': {
|
||||
// description: 'Start drawing an arc tangent to the current segment.',
|
||||
// icon: 'arc',
|
||||
// displayName: 'Tangential Arc',
|
||||
// },
|
||||
// 'Equip rectangle tool': {
|
||||
// description: 'Start drawing a rectangle.',
|
||||
// icon: 'rectangle',
|
||||
// displayName: 'Rectangle',
|
||||
// },
|
||||
'change tool': [
|
||||
{
|
||||
description: 'Start drawing straight lines.',
|
||||
icon: 'line',
|
||||
displayName: 'Line',
|
||||
args: {
|
||||
tool: {
|
||||
defaultValue: 'line',
|
||||
required: true,
|
||||
skip: true,
|
||||
inputType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Start drawing an arc tangent to the current segment.',
|
||||
icon: 'arc',
|
||||
displayName: 'Tangential Arc',
|
||||
args: {
|
||||
tool: {
|
||||
defaultValue: 'tangentialArc',
|
||||
required: true,
|
||||
skip: true,
|
||||
inputType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Start drawing a rectangle.',
|
||||
icon: 'rectangle',
|
||||
displayName: 'Rectangle',
|
||||
args: {
|
||||
tool: {
|
||||
defaultValue: 'rectangle',
|
||||
required: true,
|
||||
skip: true,
|
||||
inputType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
Export: {
|
||||
description: 'Export the current model.',
|
||||
icon: 'exportFile',
|
||||
@ -157,4 +190,36 @@ export const modelingMachineConfig: CommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
Fillet: {
|
||||
// todo
|
||||
description: 'Fillet edge',
|
||||
icon: 'fillet',
|
||||
needsReview: true,
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: [
|
||||
'default',
|
||||
'line-end',
|
||||
'line-mid',
|
||||
'extrude-wall', // to fix: accespts only this selection type
|
||||
'start-cap',
|
||||
'end-cap',
|
||||
'point',
|
||||
'edge',
|
||||
'line',
|
||||
'arc',
|
||||
'all',
|
||||
],
|
||||
multiple: true, // TODO: multiple selection like in extrude command
|
||||
required: true,
|
||||
skip: true,
|
||||
},
|
||||
radius: {
|
||||
inputType: 'kcl',
|
||||
defaultValue: KCL_DEFAULT_LENGTH,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -124,7 +124,8 @@ export function createSettingsCommand({
|
||||
displayName: `Settings · ${decamelize(type.replaceAll('.', ' · '), {
|
||||
separator: ' ',
|
||||
})}`,
|
||||
ownerMachine: 'settings',
|
||||
description: settingConfig.description,
|
||||
groupId: 'settings',
|
||||
icon: 'settings',
|
||||
needsReview: false,
|
||||
onSubmit: (data) => {
|
||||
|
||||
@ -33,13 +33,13 @@ export interface KclExpressionWithVariable extends KclExpression {
|
||||
export type KclCommandValue = KclExpression | KclExpressionWithVariable
|
||||
export type CommandInputType = (typeof INPUT_TYPES)[number]
|
||||
|
||||
export type CommandSetSchema<T extends AnyStateMachine> = Partial<{
|
||||
export type StateMachineCommandSetSchema<T extends AnyStateMachine> = Partial<{
|
||||
[EventType in EventFrom<T>['type']]: Record<string, any>
|
||||
}>
|
||||
|
||||
export type CommandSet<
|
||||
export type StateMachineCommandSet<
|
||||
T extends AllMachines,
|
||||
Schema extends CommandSetSchema<T>
|
||||
Schema extends StateMachineCommandSetSchema<T>
|
||||
> = Partial<{
|
||||
[EventType in EventFrom<T>['type']]: Command<
|
||||
T,
|
||||
@ -48,24 +48,28 @@ export type CommandSet<
|
||||
>
|
||||
}>
|
||||
|
||||
export type CommandSetConfig<
|
||||
/**
|
||||
* A configuration object for a set of commands tied to a state machine.
|
||||
* Each event type can have one or more commands associated with it.
|
||||
* @param T The state machine type.
|
||||
* @param Schema The schema for the command set, defined by the developer.
|
||||
*/
|
||||
export type StateMachineCommandSetConfig<
|
||||
T extends AllMachines,
|
||||
Schema extends CommandSetSchema<T>
|
||||
Schema extends StateMachineCommandSetSchema<T>
|
||||
> = Partial<{
|
||||
[EventType in EventFrom<T>['type']]: CommandConfig<
|
||||
T,
|
||||
EventFrom<T>['type'],
|
||||
Schema[EventType]
|
||||
>
|
||||
[EventType in EventFrom<T>['type']]:
|
||||
| CommandConfig<T, EventFrom<T>['type'], Schema[EventType]>
|
||||
| CommandConfig<T, EventFrom<T>['type'], Schema[EventType]>[]
|
||||
}>
|
||||
|
||||
export type Command<
|
||||
T extends AnyStateMachine = AnyStateMachine,
|
||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type'],
|
||||
CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName]
|
||||
CommandSchema extends StateMachineCommandSetSchema<T>[CommandName] = StateMachineCommandSetSchema<T>[CommandName]
|
||||
> = {
|
||||
name: CommandName
|
||||
ownerMachine: T['id']
|
||||
groupId: T['id']
|
||||
needsReview: boolean
|
||||
onSubmit: (data?: CommandSchema) => void
|
||||
onCancel?: () => void
|
||||
@ -81,10 +85,10 @@ export type Command<
|
||||
export type CommandConfig<
|
||||
T extends AnyStateMachine = AnyStateMachine,
|
||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type'],
|
||||
CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName]
|
||||
CommandSchema extends StateMachineCommandSetSchema<T>[CommandName] = StateMachineCommandSetSchema<T>[CommandName]
|
||||
> = Omit<
|
||||
Command<T, CommandName, CommandSchema>,
|
||||
'name' | 'ownerMachine' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
||||
'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
||||
> & {
|
||||
needsReview?: true
|
||||
args?: {
|
||||
|
||||
@ -51,6 +51,7 @@ export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
|
||||
export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
||||
SKETCH: 'sketch',
|
||||
EXTRUDE: 'extrude',
|
||||
SEGMENT: 'seg',
|
||||
} as const
|
||||
/** The default KCL length expression */
|
||||
export const KCL_DEFAULT_LENGTH = `5`
|
||||
|
||||
@ -11,20 +11,20 @@ import {
|
||||
CommandArgument,
|
||||
CommandArgumentConfig,
|
||||
CommandConfig,
|
||||
CommandSetConfig,
|
||||
CommandSetSchema,
|
||||
StateMachineCommandSetConfig,
|
||||
StateMachineCommandSetSchema,
|
||||
} from './commandTypes'
|
||||
|
||||
interface CreateMachineCommandProps<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
> {
|
||||
type: EventFrom<T>['type']
|
||||
ownerMachine: T['id']
|
||||
groupId: T['id']
|
||||
state: StateFrom<T>
|
||||
send: Function
|
||||
actor: InterpreterFrom<T>
|
||||
commandBarConfig?: CommandSetConfig<T, S>
|
||||
commandBarConfig?: StateMachineCommandSetConfig<T, S>
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
@ -32,22 +32,39 @@ interface CreateMachineCommandProps<
|
||||
// from a more terse Command Bar Meta definition.
|
||||
export function createMachineCommand<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
>({
|
||||
ownerMachine,
|
||||
groupId,
|
||||
type,
|
||||
state,
|
||||
send,
|
||||
actor,
|
||||
commandBarConfig,
|
||||
onCancel,
|
||||
}: CreateMachineCommandProps<T, S>): Command<
|
||||
T,
|
||||
typeof type,
|
||||
S[typeof type]
|
||||
> | null {
|
||||
}: CreateMachineCommandProps<T, S>):
|
||||
| Command<T, typeof type, S[typeof type]>
|
||||
| Command<T, typeof type, S[typeof type]>[]
|
||||
| null {
|
||||
const commandConfig = commandBarConfig && commandBarConfig[type]
|
||||
if (!commandConfig) return null
|
||||
// There may be no command config for this event type,
|
||||
// or there may be multiple commands to create.
|
||||
if (!commandConfig) {
|
||||
return null
|
||||
} else if (commandConfig instanceof Array) {
|
||||
return commandConfig
|
||||
.map((config) =>
|
||||
createMachineCommand({
|
||||
groupId,
|
||||
type,
|
||||
state,
|
||||
send,
|
||||
actor,
|
||||
commandBarConfig: { [type]: config },
|
||||
onCancel,
|
||||
})
|
||||
)
|
||||
.filter((c) => c !== null) as Command<T, typeof type, S[typeof type]>[]
|
||||
}
|
||||
|
||||
// Hide commands based on platform by returning `null`
|
||||
// so the consumer can filter them out
|
||||
@ -62,8 +79,9 @@ export function createMachineCommand<
|
||||
|
||||
const command: Command<T, typeof type, S[typeof type]> = {
|
||||
name: type,
|
||||
ownerMachine: ownerMachine,
|
||||
groupId,
|
||||
icon,
|
||||
description: commandConfig.description,
|
||||
needsReview: commandConfig.needsReview || false,
|
||||
onSubmit: (data?: S[typeof type]) => {
|
||||
if (data !== undefined && data !== null) {
|
||||
@ -84,6 +102,10 @@ export function createMachineCommand<
|
||||
command.onCancel = onCancel
|
||||
}
|
||||
|
||||
if ('displayName' in commandConfig) {
|
||||
command.displayName = commandConfig.displayName
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
@ -92,7 +114,7 @@ export function createMachineCommand<
|
||||
// bundled together into the args for a Command.
|
||||
function buildCommandArguments<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>,
|
||||
S extends StateMachineCommandSetSchema<T>,
|
||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type']
|
||||
>(
|
||||
state: StateFrom<T>,
|
||||
@ -112,7 +134,7 @@ function buildCommandArguments<
|
||||
|
||||
export function buildCommandArgument<
|
||||
T extends AnyStateMachine,
|
||||
O extends CommandSetSchema<T> = CommandSetSchema<T>
|
||||
O extends StateMachineCommandSetSchema<T> = StateMachineCommandSetSchema<T>
|
||||
>(
|
||||
arg: CommandArgumentConfig<O, T>,
|
||||
context: ContextFrom<T>,
|
||||
|
||||
@ -406,6 +406,17 @@ export function canExtrudeSelection(selection: Selections) {
|
||||
)
|
||||
}
|
||||
|
||||
export function canFilletSelection(selection: Selections) {
|
||||
const commonNodes = selection.codeBasedSelections.map((_, i) =>
|
||||
buildCommonNodeFromSelection(selection, i)
|
||||
) // TODO FILLET DUMMY PLACEHOLDER
|
||||
return (
|
||||
!!isSketchPipe(selection) &&
|
||||
commonNodes.every((n) => nodeHasClose(n)) &&
|
||||
commonNodes.every((n) => !nodeHasExtrude(n))
|
||||
)
|
||||
}
|
||||
|
||||
function canExtrudeSelectionItem(selection: Selections, i: number) {
|
||||
const commonNode = buildCommonNodeFromSelection(selection, i)
|
||||
|
||||
|
||||
@ -37,4 +37,7 @@ if (typeof window !== 'undefined') {
|
||||
document.addEventListener('mousemove', (e) =>
|
||||
console.log(`await page.mouse.click(${e.clientX}, ${e.clientY})`)
|
||||
)
|
||||
;(window as any).enableFillet = () => {
|
||||
;(window as any)._enableFillet = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
||||
import { Value, parse } from 'lang/wasm'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { trap } from 'lib/trap'
|
||||
import { err, trap } from 'lib/trap'
|
||||
|
||||
const isValidVariableName = (name: string) =>
|
||||
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
|
||||
@ -86,6 +86,7 @@ export function useCalculateKclExpression({
|
||||
const execAstAndSetResult = async () => {
|
||||
const _code = `const __result__ = ${value}`
|
||||
const ast = parse(_code)
|
||||
if (err(ast)) return
|
||||
if (trap(ast, { suppress: true })) return
|
||||
|
||||
const _programMem: any = { root: {}, return: null }
|
||||
|
||||
@ -57,7 +57,7 @@ export type CommandBarMachineEvent =
|
||||
}
|
||||
| {
|
||||
type: 'Find and select command'
|
||||
data: { name: string; ownerMachine: string }
|
||||
data: { name: string; groupId: string }
|
||||
}
|
||||
| {
|
||||
type: 'Change current argument'
|
||||
@ -66,7 +66,7 @@ export type CommandBarMachineEvent =
|
||||
|
||||
export const commandBarMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22Ow5wozosyLUiVNMSg5ytVKmfrIipzO564z2otPVpI1vKd18SAOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSksextGkNJBRXFUOh-f9AOA0CIKgmCABE4E3D5oyTE1-lww8clKBjZF2Bh5SFZwXUUyRVDzJwNE2LQzzYwNJE4gCgJwKNeMw6DJHJAB3LAYlM-AHjYMDuFjMCACN0B4NCMKgnD927dMkWSUs8yY8RISfWQshvcsrDlapshULJPHfZsDKM7iHPM-jJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MByXQvisP8sY8ISZwbCsDllBLSwz1qRFBQsRZ5WRIVkui-TG0M39jJ4jqLNgfLmpK3hTLIQCSFQIM2EoX8ADMjvQSQmqKna2vWvyJL3HrD1SEoyzSdJUkUm9dnUtQn10aK6hkxbiU1HVODAay3M87zE1+J6UwCtN8IQUauR0OZ0ksFwUUqRFPWmUdouPXR3TBjoIahmHKQIHKuqRrtUYSaVShhLF+TkeTzBFQosVKDRM2RVInEdSmg2p6GyE1bU9R8zrsKZqSexFqQHWlHNXHzCbFhnX1+UydFkVxiXJClmGAFEIG8hmld3ZGXp7TR5C5RSCxdjlQV1tR9bqaVdhsSFxDN5AALeOrgPa3ysJgiqcCqmr6sa7bWujxXjQd5nesQZYthsblIQYJx6hUic9AsdEFT2HQzByVLmgDJaw-eSOHPTjbysq6qcFqhrrtT9sO-4ugd1NFGc4QXEPo5eVLGsFxlnKREXGnZI9HcT1mOikO0s-S5w7bqNh9j-bkKOyQTvOy6B9utOHtj7qD1V8pSyrHJZ3STY4QmjQ0R2Ww0oSjuF3u+HAqAIBwEEOlRs48nZBVENkKQNprC42qBoJ0LothaXMJ9aElRXDLj3k3VUpJIBwOfgg4oMx8y41SPUOY8p5DFk2GiKoxsoRVmhGoM2cY2zAQRngChgU0a7HZtFH0ilRp1D5usVh6JXCKD2CUdI8lQ7rlbB8chkkJ6HkxFsBeulbQZAUCwrYCiqzIhyE+fqZtMomTMg-aCwiWYEV2NOBUCghQ6EFGzYsvpwQUT5MxawFECx2JWllRxMdLI2TsrtKMTkXIuMnmeCiZYwrePMFWCKv1XZKVGroWwmxNARK4g4hWG0tp3wSSk16lQpC41ULjV8uxUjSBvFCNEDTdCOkWCY44xCGzgzAJDaGdTnaZHBADYcqg5DpCovzeRno5izA0BeUOh8o5OPgDo+BaNvqV0xNFaE7gdYTnnvkmUChdKEMyF4LwQA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22O7JwozosyLUj3KiZZY85YtMUNgx5Ii81JuS5xBtPVCCyuVR0AOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSjPPFpDSQUP0DSQf3-QDgNAiCoJggAROBNw+aMkxNf5cMPHJSjPWRdhvZ9LGcF0b0kVQ8ycDRNkvNRWMbdjfwAoCcCjHjMOgyRyQAdywGITPwB42DA7hYzAgAjdAeDQjCoJw-du3TJE6mnWwoT0NIUTUAs71sMtdGsRYCyUtI9OJDijO49DeKw2BJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MBySy8y-LGPCElSeoy2lZJcwcNRfXHQpBWmWQsRsPYdDPZJUu-QyuPssy+Py5qSt4EyyEAkhUCDNhKF-AAzQ70EkJqiu2tqOt88S926w9UhhLlykWXFHXcTJixsd60hHGxNj2Jag01HVODAKzXI8rzE1+R6U38tN8IQJSuR0OZ0gvHQXHGxBPWmUdppUWwFV0MHJAhqGYcpAh1qwrqDx7ZFajUmVpulEbqlqREsfBTQ0jcPM5P5KmaehshNW1PVvOy7Cka7VHeoYDkyjSPQtAvPMqkRQU1BnX1+UydFkQvCWwEhqWAFEIC8xnFd3ZHntZuTDZG4ob1nGxPTUfXrBnS8nDmEpT2ObxTnXVUALeOrgPanycvKyrqpwWqGqurbWsThXjWd5WesQZYtmBkplCceplIncK0SrXYqnccxxcj5s2OQWP4-s3PzJgiqcCqmr6sa7P2x7vi6AcAvJJ7XEy0dUFMWsFxlnKfn+S5CL3E9Jiuapjv3i7qNx+T-bDskY6zourObpz+6cuZgK0Y5Ua0TqEvXDkJR-YnR0s1xjkIMnDln3uuVsHwOxT1NCjIuR5nCSBUExJi1huRqyooUTYpYnzeyUFkKsy4Tg4FQBAOAgg26Nmga7QKohshSBtNYXGDonQumtPYaQfsSjLBHDefepJICUJZtQ4oMx8wXj6jebGt4JwbC2OiVwig9hh2hLpVu0cOhxjbMBBGeABFPwSA3ERRMlhKWCn9WRVQzZQirMo0BAYNzxn4RJGBh5MRbGXsHawGQFBmLrpUasOQorOAjs0OxaUVrGVMvfaCuiVYEV2KWTYg1yxyA0i6ZYaIPSL3lOkS8wSo6hOWpxCJ8te6WRsnZKMjlnIxNgSNIiiTF6vVSdI9JM1taXlxLzTwqiClBnSqtSJScLIFVvjtKANSXquENqoJwtgyZzRFIUQ4MhqgNACdNTQCpLbWyshMnsjpSwjXRJYHecgcmImsLIz0odNAaGqPiHpDYY6HwTlE+ATiqHPwohYewWQshmGhHrCcJz5Bck5toZwGhMheC8EAA */
|
||||
predictableActionArguments: true,
|
||||
tsTypes: {} as import('./commandBarMachine.typegen').Typegen0,
|
||||
context: {
|
||||
@ -120,9 +120,7 @@ export const commandBarMachine = createMachine(
|
||||
context.commands.filter(
|
||||
(c) =>
|
||||
!event.data.commands.some(
|
||||
(c2) =>
|
||||
c2.name === c.name &&
|
||||
c2.ownerMachine === c.ownerMachine
|
||||
(c2) => c2.name === c.name && c2.groupId === c.groupId
|
||||
)
|
||||
),
|
||||
}),
|
||||
@ -149,6 +147,10 @@ export const commandBarMachine = createMachine(
|
||||
cond: 'Command has no arguments',
|
||||
actions: ['Execute command'],
|
||||
},
|
||||
{
|
||||
target: 'Checking Arguments',
|
||||
cond: 'All arguments are skippable',
|
||||
},
|
||||
{
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument to first non-skippable'],
|
||||
@ -201,7 +203,7 @@ export const commandBarMachine = createMachine(
|
||||
'Change current argument': {
|
||||
target: 'Gathering arguments',
|
||||
internal: true,
|
||||
actions: ['Remove current argument and set a new one'],
|
||||
actions: ['Set current argument'],
|
||||
},
|
||||
|
||||
'Deselect command': {
|
||||
@ -357,29 +359,13 @@ export const commandBarMachine = createMachine(
|
||||
switch (event.type) {
|
||||
case 'Edit argument':
|
||||
return event.data.arg
|
||||
case 'Change current argument':
|
||||
return Object.values(event.data)[0]
|
||||
default:
|
||||
return context.currentArgument
|
||||
}
|
||||
},
|
||||
}),
|
||||
'Remove current argument and set a new one': assign({
|
||||
argumentsToSubmit: (context, event) => {
|
||||
if (
|
||||
event.type !== 'Change current argument' ||
|
||||
!context.currentArgument
|
||||
)
|
||||
return context.argumentsToSubmit
|
||||
const { name } = context.currentArgument
|
||||
|
||||
const { [name]: _, ...rest } = context.argumentsToSubmit
|
||||
return rest
|
||||
},
|
||||
currentArgument: (context, event) => {
|
||||
if (event.type !== 'Change current argument')
|
||||
return context.currentArgument
|
||||
return Object.values(event.data)[0]
|
||||
},
|
||||
}),
|
||||
'Clear argument data': assign({
|
||||
selectedCommand: undefined,
|
||||
currentArgument: undefined,
|
||||
@ -393,9 +379,7 @@ export const commandBarMachine = createMachine(
|
||||
selectedCommand: (c, e) => {
|
||||
if (e.type !== 'Find and select command') return c.selectedCommand
|
||||
const found = c.commands.find(
|
||||
(cmd) =>
|
||||
cmd.name === e.data.name &&
|
||||
cmd.ownerMachine === e.data.ownerMachine
|
||||
(cmd) => cmd.name === e.data.name && cmd.groupId === e.data.groupId
|
||||
)
|
||||
|
||||
return !!found ? found : c.selectedCommand
|
||||
@ -514,7 +498,9 @@ export const commandBarMachine = createMachine(
|
||||
)
|
||||
|
||||
function sortCommands(a: Command, b: Command) {
|
||||
if (b.ownerMachine === 'auth') return -1
|
||||
if (a.ownerMachine === 'auth') return 1
|
||||
if (b.groupId === 'auth' && !(a.groupId === 'auth')) return -2
|
||||
if (a.groupId === 'auth' && !(b.groupId === 'auth')) return 2
|
||||
if (b.groupId === 'settings' && !(a.groupId === 'settings')) return -1
|
||||
if (a.groupId === 'settings' && !(b.groupId === 'settings')) return 1
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
|
||||
82
src/wasm-lib/Cargo.lock
generated
@ -169,7 +169,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -180,7 +180,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -191,7 +191,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -431,7 +431,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -631,7 +631,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -642,7 +642,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -697,7 +697,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -726,7 +726,7 @@ dependencies = [
|
||||
"rustfmt-wrapper",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -737,7 +737,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -764,7 +764,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -936,7 +936,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1026,7 +1026,7 @@ dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1448,7 +1448,7 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1824,7 +1824,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.3",
|
||||
"structmeta",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1842,7 +1842,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"either",
|
||||
"fnv",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"quick-xml",
|
||||
@ -1877,7 +1877,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2041,7 +2041,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2054,7 +2054,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2516,7 +2516,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2590,7 +2590,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2601,7 +2601,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2624,7 +2624,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2645,7 +2645,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2782,7 +2782,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2793,7 +2793,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2837,9 +2837,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.70"
|
||||
version = "2.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16"
|
||||
checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2860,7 +2860,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2928,22 +2928,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3039,7 +3039,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3192,7 +3192,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3220,7 +3220,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3297,7 +3297,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -3455,7 +3455,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3516,7 +3516,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -3551,7 +3551,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -3876,7 +3876,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -20,7 +20,7 @@ quote = "1"
|
||||
regex = "1.10"
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.70", features = ["full"] }
|
||||
syn = { version = "2.0.71", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.86"
|
||||
|
||||
@ -837,7 +837,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
|
||||
// Read the output file.
|
||||
let actual = image::io::Reader::open(output_file).unwrap().decode().unwrap();
|
||||
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #test_name_str), &actual, 1.0);
|
||||
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #test_name_str), &actual, 0.99);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_someFn {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_someFn {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show1"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ mod test_examples_my_func {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_my_func0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
|
||||
@ -178,7 +178,7 @@ mod test_examples_my_func {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_my_func1"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ mod test_examples_line_to {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_line_to0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
|
||||
@ -178,7 +178,7 @@ mod test_examples_line_to {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_line_to1"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_min {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_min0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ mod test_examples_min {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_min1"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_import {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_import {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_import {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ databake = "0.1.8"
|
||||
kcl-lib = { path = "../kcl" }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2.0.70", features = ["full"] }
|
||||
syn = { version = "2.0.71", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
@ -35,7 +35,7 @@ schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"]
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde_json = "1.0.120"
|
||||
sha2 = "0.10.8"
|
||||
thiserror = "1.0.61"
|
||||
thiserror = "1.0.62"
|
||||
toml = "0.8.14"
|
||||
ts-rs = { version = "9.0.1", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
|
||||
url = { version = "2.5.2", features = ["serde"] }
|
||||
@ -98,6 +98,10 @@ harness = false
|
||||
name = "compiler_benchmark_iai"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "digest_benchmark"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "lsp_semantic_tokens_benchmark_criterion"
|
||||
harness = false
|
||||
|
||||
31
src/wasm-lib/kcl/benches/digest_benchmark.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
pub fn bench_digest(c: &mut Criterion) {
|
||||
for (name, file) in [
|
||||
("pipes_on_pipes", PIPES_PROGRAM),
|
||||
("big_kitt", KITT_PROGRAM),
|
||||
("cube", CUBE_PROGRAM),
|
||||
("math", MATH_PROGRAM),
|
||||
("mike_stress_test", MIKE_STRESS_TEST_PROGRAM),
|
||||
] {
|
||||
let tokens = kcl_lib::token::lexer(file).unwrap();
|
||||
let prog = kcl_lib::parser::Parser::new(tokens).ast().unwrap();
|
||||
c.bench_function(&format!("digest_{name}"), move |b| {
|
||||
let prog = prog.clone();
|
||||
|
||||
b.iter(move || {
|
||||
let mut prog = prog.clone();
|
||||
prog.compute_digest();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_digest);
|
||||
criterion_main!(benches);
|
||||
|
||||
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
|
||||
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
|
||||
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
|
||||
const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl");
|
||||
const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../../tests/executor/inputs/mike_stress_test.kcl");
|
||||
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 333 KiB |
@ -4,6 +4,10 @@ use kcl_lib::{
|
||||
settings::types::UnitLength,
|
||||
};
|
||||
|
||||
/// The minimum permissible difference between asserted twenty-twenty images.
|
||||
/// i.e. how different the current model snapshot can be from the previous saved one.
|
||||
const MIN_DIFF: f64 = 0.99;
|
||||
|
||||
// mod server;
|
||||
|
||||
async fn new_context(units: UnitLength) -> Result<ExecutorContext> {
|
||||
@ -83,35 +87,35 @@ const part002 = startSketchOn(part001, here)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_riddle_small() {
|
||||
let code = include_str!("inputs/riddle_small.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/riddle_small.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/riddle_small.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_lego() {
|
||||
let code = include_str!("inputs/lego.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/lego.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/lego.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_pipe_as_arg() {
|
||||
let code = include_str!("inputs/pipe_as_arg.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pipe_as_arg.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pipe_as_arg.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_pentagon_fillet_sugar() {
|
||||
let code = include_str!("inputs/pentagon_fillet_sugar.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Cm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pentagon_fillet_sugar.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pentagon_fillet_sugar.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -139,14 +143,14 @@ const part002 = startSketchOn(part001, "start")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_start.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_start.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_mike_stress_lines() {
|
||||
let code = include_str!("inputs/mike_stress_test.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/mike_stress_test.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/mike_stress_test.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -174,7 +178,7 @@ const part002 = startSketchOn(part001, "END")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_end.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_end.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -205,7 +209,7 @@ const part002 = startSketchOn(part001, "END")
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/sketch_on_face_end_negative_extrude.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -242,7 +246,7 @@ async fn serial_test_basic_fillet_cube_start() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_start.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_start.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -259,7 +263,7 @@ async fn serial_test_basic_fillet_cube_end() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_end.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_end.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -279,7 +283,7 @@ async fn serial_test_basic_fillet_cube_close_opposite() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/basic_fillet_cube_close_opposite.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -299,7 +303,7 @@ async fn serial_test_basic_fillet_cube_next_adjacent() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/basic_fillet_cube_next_adjacent.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -319,7 +323,7 @@ async fn serial_test_basic_fillet_cube_previous_adjacent() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/basic_fillet_cube_previous_adjacent.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -341,7 +345,7 @@ const fnBox = box(3, 6, 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -364,7 +368,7 @@ const thing = box([0,0], 3, 6, 10)"#;
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/function_sketch_with_position.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -382,7 +386,7 @@ async fn serial_test_execute_with_angled_line() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -408,7 +412,7 @@ const bracket = startSketchOn('XY')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/parametric.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/parametric.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -442,7 +446,7 @@ const bracket = startSketchAt([0, 0])
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/parametric_with_tan_arc.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/parametric_with_tan_arc.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -470,7 +474,7 @@ async fn serial_test_execute_i_shape() {
|
||||
let code = include_str!("inputs/i_shape.kcl");
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/i_shape.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/i_shape.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -479,7 +483,7 @@ async fn serial_test_execute_pipes_on_pipes() {
|
||||
let code = include_str!("inputs/pipes_on_pipes.kcl");
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pipes_on_pipes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pipes_on_pipes.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -487,7 +491,7 @@ async fn serial_test_execute_cylinder() {
|
||||
let code = include_str!("inputs/cylinder.kcl");
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cylinder.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cylinder.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -495,7 +499,7 @@ async fn serial_test_execute_kittycad_svg() {
|
||||
let code = include_str!("inputs/kittycad_svg.kcl");
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/kittycad_svg.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/kittycad_svg.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -523,7 +527,7 @@ const pt2 = b2.value[0]
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/member_expression_sketch_group.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -536,7 +540,7 @@ async fn serial_test_helix_defaults() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_defaults.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_defaults.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -551,7 +555,7 @@ async fn serial_test_helix_defaults_negative_extrude() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/helix_defaults_negative_extrude.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -564,7 +568,7 @@ async fn serial_test_helix_ccw() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_ccw.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_ccw.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -576,7 +580,7 @@ async fn serial_test_helix_with_length() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_with_length.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_with_length.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -591,7 +595,7 @@ async fn serial_test_dimensions_match() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/dimensions_match.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/dimensions_match.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -608,7 +612,7 @@ const body = startSketchOn('XY')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/close_arc.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/close_arc.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -634,7 +638,7 @@ let thing = box(-12, -15, 10)
|
||||
box(-20, -5, 10)"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/negative_args.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/negative_args.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -647,7 +651,7 @@ async fn serial_test_basic_tangential_arc() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -660,7 +664,11 @@ async fn serial_test_basic_tangential_arc_with_point() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc_with_point.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/tangential_arc_with_point.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -673,7 +681,7 @@ async fn serial_test_basic_tangential_arc_to() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc_to.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc_to.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -703,7 +711,7 @@ box(-20, -5, 10, 'xy')"#;
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/different_planes_same_drawing.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -762,7 +770,7 @@ const part004 = startSketchOn('YZ')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/lots_of_planes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/lots_of_planes.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -779,7 +787,7 @@ async fn serial_test_holes() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/holes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/holes.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -798,7 +806,7 @@ async fn optional_params() {
|
||||
const thing = other_circle([2, 2], 20)
|
||||
"#;
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/optional_params.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/optional_params.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -834,7 +842,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/rounded_with_holes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/rounded_with_holes.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -842,7 +850,7 @@ async fn serial_test_top_level_expression() {
|
||||
let code = r#"startSketchOn('XY') |> circle([0,0], 22, %) |> extrude(14, %)"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/top_level_expression.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/top_level_expression.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -859,7 +867,7 @@ const part = startSketchOn('XY')
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_linear_basic_with_math.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -872,7 +880,7 @@ async fn serial_test_patterns_linear_basic() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -888,7 +896,7 @@ async fn serial_test_patterns_linear_basic_3d() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_3d.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_3d.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -903,7 +911,7 @@ async fn serial_test_patterns_linear_basic_negative_distance() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_linear_basic_negative_distance.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -919,7 +927,7 @@ async fn serial_test_patterns_linear_basic_negative_axis() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_linear_basic_negative_axis.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -941,7 +949,11 @@ const rectangle = startSketchOn('XY')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_holes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_linear_basic_holes.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -953,7 +965,11 @@ async fn serial_test_patterns_circular_basic_2d() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_circular_basic_2d.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_circular_basic_2d.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -969,7 +985,11 @@ async fn serial_test_patterns_circular_basic_3d() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_circular_basic_3d.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_circular_basic_3d.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -988,7 +1008,7 @@ async fn serial_test_patterns_circular_3d_tilted_axis() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_circular_3d_tilted_axis.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1009,7 +1029,7 @@ async fn serial_test_import_obj_with_mtl() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.obj")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1017,7 +1037,11 @@ async fn serial_test_import_obj_with_mtl_units() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.obj", {type: "obj", units: "m"})"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl_units.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/import_obj_with_mtl_units.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1025,7 +1049,7 @@ async fn serial_test_import_gltf_with_bin() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.gltf")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_with_bin.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_with_bin.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1033,7 +1057,7 @@ async fn serial_test_import_gltf_embedded() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube-embedded.gltf")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_embedded.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_embedded.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1041,7 +1065,7 @@ async fn serial_test_import_glb() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.glb")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1049,7 +1073,7 @@ async fn serial_test_import_glb_no_assign() {
|
||||
let code = r#"import("tests/executor/inputs/cube.glb")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb_no_assign.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb_no_assign.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1082,7 +1106,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_mm.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_mm.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1103,7 +1127,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Cm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_cm.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_cm.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1124,7 +1148,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::M).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_m.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_m.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1145,7 +1169,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::In).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_in.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_in.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1166,7 +1190,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Ft).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_ft.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_ft.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1187,7 +1211,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Yd).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_yd.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_yd.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1256,7 +1280,7 @@ const part003 = startSketchOn(part002, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_of_face.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_of_face.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1301,7 +1325,7 @@ const part002 = startSketchOn(part001, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_circle.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_circle.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1325,7 +1349,11 @@ const part002 = startSketchOn(part001, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_circle_tagged.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/sketch_on_face_circle_tagged.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1391,7 +1419,7 @@ async fn serial_test_big_number_angle_to_match_length_x() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/big_number_angle_to_match_length_x.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1412,7 +1440,7 @@ async fn serial_test_big_number_angle_to_match_length_y() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/big_number_angle_to_match_length_y.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1433,7 +1461,7 @@ async fn serial_test_simple_revolve() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1453,7 +1481,7 @@ async fn serial_test_simple_revolve_uppercase() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_uppercase.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_uppercase.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1473,7 +1501,7 @@ async fn serial_test_simple_revolve_negative() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_negative.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_negative.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1543,7 +1571,11 @@ async fn serial_test_simple_revolve_custom_angle() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_custom_angle.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/simple_revolve_custom_angle.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1563,7 +1595,11 @@ async fn serial_test_simple_revolve_custom_axis() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_custom_axis.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/simple_revolve_custom_axis.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1587,7 +1623,7 @@ const sketch001 = startSketchOn(box, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_edge.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_edge.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1638,7 +1674,11 @@ const sketch001 = startSketchOn(box, "END")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face_circle_edge.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/revolve_on_face_circle_edge.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1660,7 +1700,7 @@ const sketch001 = startSketchOn(box, "END")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face_circle.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face_circle.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1686,7 +1726,7 @@ const sketch001 = startSketchOn(box, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1700,7 +1740,7 @@ async fn serial_test_basic_revolve_circle() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_revolve_circle.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_revolve_circle.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1727,7 +1767,11 @@ const part002 = startSketchOn(part001, 'end')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_sketch_on_edge.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/simple_revolve_sketch_on_edge.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1788,7 +1832,7 @@ const plumbus1 = make_circle(p, b,$arc_b, [0, 0], 2.5)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/plumbus_fillets.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/plumbus_fillets.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1826,7 +1870,11 @@ capScrew([0, 0.5, 0], 50, 37.5, 50, 25)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/member_expression_in_params.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/member_expression_in_params.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1944,7 +1992,7 @@ async fn serial_test_xz_plane() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/xz_plane.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/xz_plane.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1958,7 +2006,7 @@ async fn serial_test_neg_xz_plane() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/neg_xz_plane.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/neg_xz_plane.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1985,7 +2033,11 @@ const pattn2 = patternLinear3d({
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/linear_pattern3d_a_pattern.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/linear_pattern3d_a_pattern.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2008,7 +2060,11 @@ const pattn2 = patternCircular3d({axis: [0,0, 1], center: [-20, -20, -20], repet
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/circular_pattern3d_a_pattern.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/circular_pattern3d_a_pattern.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2037,7 +2093,7 @@ extrude(10, sketch001)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/array_of_sketches.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/array_of_sketches.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2103,7 +2159,7 @@ const sketch001 = startSketchOn(bracket, 'seg01')
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/sketch_on_face_after_fillets_referencing_face.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2140,7 +2196,11 @@ const pattn1 = patternLinear3d({
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pattern3d_array_of_extrudes.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/pattern3d_array_of_extrudes.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2191,7 +2251,7 @@ const baseExtrusion = extrude(width, sketch001)
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/fillets_referencing_other_fillets.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2243,7 +2303,7 @@ const baseExtrusion = extrude(width, sketch001)
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/chamfers_referencing_other_chamfers.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2302,7 +2362,7 @@ const pattn1 = patternLinear3d({
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/linear_pattern3d_filleted_sketch.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2333,7 +2393,7 @@ const pattn2 = patternCircular3d({axis: [0,0, 1], center: [-20, -20, -20], repet
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/circular_pattern3d_filleted_sketch.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2364,7 +2424,7 @@ const pattn2 = patternCircular3d({axis: [0,0, 1], center: [-20, -20, -20], repet
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/circular_pattern3d_chamfered_sketch.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2429,7 +2489,11 @@ const sketch001 = startSketchOn(part001, 'chamfer1')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_of_chamfer.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/sketch_on_face_of_chamfer.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2463,21 +2527,21 @@ let p = triangle(200)
|
||||
async fn serial_test_global_tags() {
|
||||
let code = include_str!("inputs/global-tags.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/global_tags.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/global_tags.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_pattern_vase() {
|
||||
let code = include_str!("inputs/pattern_vase.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pattern_vase.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pattern_vase.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_scoped_tags() {
|
||||
let code = include_str!("inputs/scoped-tags.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/scoped_tags.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/scoped_tags.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2487,7 +2551,7 @@ async fn serial_test_order_sketch_extrude_in_order() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/order-sketch-extrude-in-order.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2498,7 +2562,7 @@ async fn serial_test_order_sketch_extrude_out_of_order() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/order-sketch-extrude-out-of-order.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2506,5 +2570,5 @@ async fn serial_test_order_sketch_extrude_out_of_order() {
|
||||
async fn serial_test_extrude_custom_plane() {
|
||||
let code = include_str!("inputs/extrude-custom-plane.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/extrude-custom-plane.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/extrude-custom-plane.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 333 KiB |