Compare commits
176 Commits
Author | SHA1 | Date | |
---|---|---|---|
b17e61d963 | |||
d31d07d9c8 | |||
7aa2d63c21 | |||
e1081b0ee6 | |||
59223279b7 | |||
8a4e717565 | |||
80b542ca18 | |||
e4bfc863ea | |||
77ef255de4 | |||
64c3841079 | |||
c7bb6bc845 | |||
1af8a8c64f | |||
eb4776826b | |||
f3dd0469d5 | |||
deea74754d | |||
3fd798c704 | |||
cc9eaf2991 | |||
6f24031220 | |||
672bcd297f | |||
3bc182fe16 | |||
589cd39eec | |||
63feebef5c | |||
65037abd9a | |||
97bc339a62 | |||
4e9a6375a5 | |||
3d19dfb800 | |||
d2a7b84292 | |||
9e02bab155 | |||
7352de5a70 | |||
9797d0cb81 | |||
83907fa9db | |||
a367be4e2b | |||
056fa00adc | |||
4759fb2e6f | |||
45f497d9cd | |||
dc61bdebdf | |||
61943055e5 | |||
416fe0f644 | |||
708465d818 | |||
e706fb02d6 | |||
1bf7daa474 | |||
ffc47f8f40 | |||
768aaa84f6 | |||
f3a700eec8 | |||
c853637a9a | |||
9af30d9ef6 | |||
6164714a6b | |||
64ceb98eba | |||
2cbf260900 | |||
cfaaedf602 | |||
12b3717eb5 | |||
0bc685b0c4 | |||
9ee032771a | |||
c307ddd1b1 | |||
a30818ff2b | |||
53e763d938 | |||
8f74cd1d0c | |||
c271942897 | |||
a03d09b41d | |||
2971b7752b | |||
70e99eb00b | |||
5c66af59d2 | |||
6dda6daeef | |||
b5387f1220 | |||
fd5921b366 | |||
716ad938fc | |||
40136eb392 | |||
8d2b89fcd1 | |||
ad9fba3390 | |||
911c43af50 | |||
ab4e04f6c2 | |||
94aef05f74 | |||
d820cf2446 | |||
0c724c4971 | |||
b54ac4a694 | |||
27227092b1 | |||
04e1b92a5b | |||
0553cd4621 | |||
61a0c88af4 | |||
d5b0544437 | |||
6cc8af5c23 | |||
888104080e | |||
b6769889e3 | |||
a32258dac4 | |||
18dbbad244 | |||
b67c16cc9d | |||
ad482641ef | |||
9ee24845a1 | |||
e69d263252 | |||
111738f38e | |||
e34501cc5a | |||
c767c1c3a6 | |||
e399a8f938 | |||
59d5f2524a | |||
b47ebd14d2 | |||
e74bcd0695 | |||
22161ec386 | |||
ada46c4317 | |||
6675fa8d1e | |||
075d2debce | |||
488e41ac0e | |||
8147f5f1eb | |||
bc7e9d9789 | |||
8d493d6517 | |||
9fa98d6f3f | |||
24a31c94e7 | |||
76e3207251 | |||
e2237fa9f6 | |||
ae4aa82129 | |||
14b287a746 | |||
dd1b7631fa | |||
f98f782b40 | |||
01f5ecdc36 | |||
5297d3e142 | |||
f71f44968b | |||
7b79998c40 | |||
4632d407c1 | |||
58d7e59ca4 | |||
f592d8db84 | |||
31eca3728e | |||
c5d8779af4 | |||
cf686bdeb0 | |||
ae7143a94f | |||
f2b24849b3 | |||
35d6530406 | |||
01208221c7 | |||
fbbed3fbfb | |||
ce51f26701 | |||
caddac5059 | |||
54751aa7bb | |||
7b7d5e5f5e | |||
f7971bddef | |||
e4f2e66029 | |||
663c396128 | |||
8db86a6783 | |||
d7ad7c749e | |||
6e3c642d22 | |||
4d7433ff3a | |||
4e93146559 | |||
731a9bfbdb | |||
cdb4c36cf5 | |||
66ba60dc8e | |||
8fcc8cdd17 | |||
bba9bdc563 | |||
760a180f56 | |||
0eeff8cb45 | |||
3c76721159 | |||
6ac79ae645 | |||
90d7c33c92 | |||
e02bc76bdb | |||
0466f04d82 | |||
f8ed830b60 | |||
b7ca91bf6d | |||
2261f92b0b | |||
bbe9e621b1 | |||
bf087d760b | |||
a4353c63fd | |||
c438d11c3d | |||
43284e33c8 | |||
77dce7f0dd | |||
d559862051 | |||
7382ed87ba | |||
3324ed31de | |||
ba9dbc2205 | |||
b0028d4874 | |||
9e6be9651c | |||
b145ab0106 | |||
84e0fbb70f | |||
990605bbea | |||
d075c4ad13 | |||
a3f41f5519 | |||
cb173e2850 | |||
87cd3b67f4 | |||
fe3ee3806e | |||
c9ed6c724c | |||
a5fa259d55 |
@ -1,6 +1,6 @@
|
|||||||
VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.kittycad.io
|
VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
|
||||||
VITE_KC_SITE_BASE_URL=https://kittycad.io
|
VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
VITE_KC_SENTRY_DSN=
|
VITE_KC_SENTRY_DSN=
|
||||||
|
5
.github/workflows/cargo-build.yml
vendored
5
.github/workflows/cargo-build.yml
vendored
@ -15,6 +15,9 @@ on:
|
|||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
- '**/rust-toolchain.toml'
|
- '**/rust-toolchain.toml'
|
||||||
- .github/workflows/cargo-build.yml
|
- .github/workflows/cargo-build.yml
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
name: cargo build
|
name: cargo build
|
||||||
jobs:
|
jobs:
|
||||||
cargobuild:
|
cargobuild:
|
||||||
@ -24,7 +27,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
dir: ['src/wasm-lib']
|
dir: ['src/wasm-lib']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
7
.github/workflows/cargo-clippy.yml
vendored
7
.github/workflows/cargo-clippy.yml
vendored
@ -15,6 +15,9 @@ on:
|
|||||||
- '**/rust-toolchain.toml'
|
- '**/rust-toolchain.toml'
|
||||||
- '**.rs'
|
- '**.rs'
|
||||||
- .github/workflows/cargo-build.yml
|
- .github/workflows/cargo-build.yml
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
name: cargo clippy
|
name: cargo clippy
|
||||||
jobs:
|
jobs:
|
||||||
cargoclippy:
|
cargoclippy:
|
||||||
@ -24,7 +27,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
dir: ['src/wasm-lib']
|
dir: ['src/wasm-lib']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@ -54,4 +57,4 @@ jobs:
|
|||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: |
|
run: |
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo clippy --all --tests -- -D warnings
|
cargo clippy --all --tests --benches -- -D warnings
|
||||||
|
40
.github/workflows/cargo-criterion.yml
vendored
Normal file
40
.github/workflows/cargo-criterion.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- '**.rs'
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
- '**/Cargo.lock'
|
||||||
|
- '**/rust-toolchain.toml'
|
||||||
|
- .github/workflows/cargo-criterion.yml
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '**.rs'
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
- '**/Cargo.lock'
|
||||||
|
- '**/rust-toolchain.toml'
|
||||||
|
- .github/workflows/cargo-criterion.yml
|
||||||
|
workflow_dispatch:
|
||||||
|
permissions: read-all
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
name: cargo criterion
|
||||||
|
jobs:
|
||||||
|
cargocriterion:
|
||||||
|
name: cargo criterion
|
||||||
|
runs-on: ubuntu-latest-8-cores
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cargo install cargo-criterion
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2.6.1
|
||||||
|
- name: Benchmark kcl library
|
||||||
|
shell: bash
|
||||||
|
run: |-
|
||||||
|
cd src/wasm-lib/kcl; cargo criterion
|
||||||
|
|
5
.github/workflows/cargo-fmt.yml
vendored
5
.github/workflows/cargo-fmt.yml
vendored
@ -18,6 +18,9 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
packages: read
|
packages: read
|
||||||
contents: read
|
contents: read
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
name: cargo fmt
|
name: cargo fmt
|
||||||
jobs:
|
jobs:
|
||||||
cargofmt:
|
cargofmt:
|
||||||
@ -27,7 +30,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
dir: ['src/wasm-lib', 'src-tauri']
|
dir: ['src/wasm-lib', 'src-tauri']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
8
.github/workflows/cargo-test.yml
vendored
8
.github/workflows/cargo-test.yml
vendored
@ -17,6 +17,9 @@ on:
|
|||||||
- .github/workflows/cargo-test.yml
|
- .github/workflows/cargo-test.yml
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
name: cargo test
|
name: cargo test
|
||||||
jobs:
|
jobs:
|
||||||
cargotest:
|
cargotest:
|
||||||
@ -26,7 +29,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
dir: ['src/wasm-lib']
|
dir: ['src/wasm-lib']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@ -55,7 +58,8 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo test --all
|
cargo nextest run --workspace --no-fail-fast -P ci
|
||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
|
RUST_MIN_STACK: 10485760000
|
||||||
|
|
||||||
|
77
.github/workflows/ci.yml
vendored
77
.github/workflows/ci.yml
vendored
@ -4,16 +4,18 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
check-format:
|
check-format:
|
||||||
runs-on: 'ubuntu-20.04'
|
runs-on: 'ubuntu-20.04'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
@ -25,7 +27,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
@ -33,19 +35,17 @@ jobs:
|
|||||||
- run: yarn install
|
- run: yarn install
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "./src/wasm-lib"
|
workspaces: './src/wasm-lib'
|
||||||
|
|
||||||
- run: yarn build:wasm
|
- run: yarn build:wasm
|
||||||
- run: yarn tsc
|
- run: yarn tsc
|
||||||
|
|
||||||
|
|
||||||
build-test-web:
|
build-test-web:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.export_version.outputs.version }}
|
version: ${{ steps.export_version.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
@ -56,7 +56,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "./src/wasm-lib"
|
workspaces: './src/wasm-lib'
|
||||||
|
|
||||||
- run: yarn build:wasm
|
- run: yarn build:wasm
|
||||||
|
|
||||||
@ -69,7 +69,6 @@ jobs:
|
|||||||
- id: export_version
|
- id: export_version
|
||||||
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
|
||||||
build-apps:
|
build-apps:
|
||||||
needs: [check-format, build-test-web, check-types]
|
needs: [check-format, build-test-web, check-types]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@ -77,8 +76,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-20.04, windows-latest]
|
os: [macos-latest, ubuntu-20.04, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: install ubuntu system dependencies
|
- name: install ubuntu system dependencies
|
||||||
if: matrix.os == 'ubuntu-20.04'
|
if: matrix.os == 'ubuntu-20.04'
|
||||||
@ -104,7 +102,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "./src/wasm-lib"
|
workspaces: './src/wasm-lib'
|
||||||
|
|
||||||
- name: wasm prep
|
- name: wasm prep
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -114,18 +112,6 @@ jobs:
|
|||||||
cd ../../
|
cd ../../
|
||||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||||
|
|
||||||
- name: macos sed
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sed -i '' 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
|
|
||||||
|
|
||||||
- name: ubuntu and windows sed
|
|
||||||
if: matrix.os != 'macos-latest'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sed -i 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
|
|
||||||
|
|
||||||
- name: Fix format
|
- name: Fix format
|
||||||
run: yarn fmt
|
run: yarn fmt
|
||||||
|
|
||||||
@ -134,11 +120,42 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
rustup target add aarch64-apple-darwin
|
rustup target add aarch64-apple-darwin
|
||||||
|
|
||||||
- name: Build the app for the current platform (no upload)
|
- name: Prepare Windows certificate and variables
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
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 Windows certicate with SSM KSP
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
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 and sign the app for the current platform
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
with:
|
with:
|
||||||
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
|
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
|
||||||
|
|
||||||
@ -146,7 +163,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
|
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
|
||||||
|
|
||||||
|
|
||||||
publish-apps-release:
|
publish-apps-release:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
@ -156,7 +172,6 @@ jobs:
|
|||||||
PUB_DATE: ${{ github.event.release.created_at }}
|
PUB_DATE: ${{ github.event.release.created_at }}
|
||||||
NOTES: ${{ github.event.release.body }}
|
NOTES: ${{ github.event.release.body }}
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
|
|
||||||
- name: Generate the update static endpoint
|
- name: Generate the update static endpoint
|
||||||
@ -164,7 +179,7 @@ jobs:
|
|||||||
ls -l artifact/*/*itty*
|
ls -l artifact/*/*itty*
|
||||||
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
||||||
LINUX_SIG=`cat artifact/appimage/*.AppImage.tar.gz.sig`
|
LINUX_SIG=`cat artifact/appimage/*.AppImage.tar.gz.sig`
|
||||||
WINDOWS_SIG=`cat artifact/nsis/*.nsis.zip.sig`
|
WINDOWS_SIG=`cat artifact/msi/*.msi.zip.sig`
|
||||||
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
|
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
|
||||||
jq --null-input \
|
jq --null-input \
|
||||||
--arg version "v${VERSION_NO_V}" \
|
--arg version "v${VERSION_NO_V}" \
|
||||||
@ -175,7 +190,7 @@ jobs:
|
|||||||
--arg linux_sig "$LINUX_SIG" \
|
--arg linux_sig "$LINUX_SIG" \
|
||||||
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage.tar.gz" \
|
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage.tar.gz" \
|
||||||
--arg windows_sig "$WINDOWS_SIG" \
|
--arg windows_sig "$WINDOWS_SIG" \
|
||||||
--arg windows_url "$RELEASE_DIR/nsis/KittyCAD%20Modeling_${VERSION_NO_V}_x64-setup.nsis.zip" \
|
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
||||||
'{
|
'{
|
||||||
"version": $version,
|
"version": $version,
|
||||||
"pub_date": $pub_date,
|
"pub_date": $pub_date,
|
||||||
@ -210,7 +225,7 @@ jobs:
|
|||||||
--arg notes "${NOTES}" \
|
--arg notes "${NOTES}" \
|
||||||
--arg darwin_url "$RELEASE_DIR/dmg/KittyCAD%20Modeling_${VERSION_NO_V}_universal.dmg" \
|
--arg darwin_url "$RELEASE_DIR/dmg/KittyCAD%20Modeling_${VERSION_NO_V}_universal.dmg" \
|
||||||
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage" \
|
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage" \
|
||||||
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi" \
|
||||||
'{
|
'{
|
||||||
"version": $version,
|
"version": $version,
|
||||||
"pub_date": $pub_date,
|
"pub_date": $pub_date,
|
||||||
|
2
.github/workflows/update-dev-branch.yml
vendored
2
.github/workflows/update-dev-branch.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.0
|
- uses: actions/checkout@v4
|
||||||
- shell: bash
|
- shell: bash
|
||||||
run: |
|
run: |
|
||||||
# checkout our branch
|
# checkout our branch
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -22,6 +22,11 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
src/wasm-lib/.idea
|
||||||
|
src/wasm-lib/.vscode
|
||||||
|
|
||||||
# rust
|
# rust
|
||||||
src/wasm-lib/target
|
src/wasm-lib/target
|
||||||
src/wasm-lib/bindings
|
src/wasm-lib/bindings
|
||||||
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2023 The KittyCAD Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
115
README.md
115
README.md
@ -1,48 +1,72 @@
|
|||||||
## Kurt demo project
|

|
||||||
|
|
||||||
|
## KittyCAD Modeling App
|
||||||
|
|
||||||
live at [app.kittycad.io](https://app.kittycad.io/)
|
live at [app.kittycad.io](https://app.kittycad.io/)
|
||||||
|
|
||||||
Not sure what to call this, it's both a language/interpreter and a UI that uses the language as the source of truth model the user build with direct-manipulation with the UI.
|
A CAD application from the future, brought to you by the [KittyCAD team](https://kittycad.io).
|
||||||
|
|
||||||
It might make sense to split this repo up at some point, but not the lang and the UI are all togther in a react app
|
The KittyCAD modeling app is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence:
|
||||||
|
|
||||||
Originally Presented on 10/01/2023
|
- All artifacts—including parts and assemblies—should be represented as human-readable code. At the end of the day, your CAD project should be "plain text"
|
||||||
|
- This makes version control—which is a solved problem in software engineering—trivial for CAD
|
||||||
|
- All GUI (or point-and-click) interactions should be actions performed on this code representation under the hood
|
||||||
|
- This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in KittyCAD Modeling App
|
||||||
|
- Everything graphics _has_ to be built for the GPU
|
||||||
|
- Most CAD applications have had to retrofit support for GPUs, but our geometry engine is made for GPUs (primarily Nvidia's Vulkan), getting the order of magnitude rendering performance boost with it
|
||||||
|
- Make the resource-intensive pieces of an application auto-scaling
|
||||||
|
- One of the bottlenecks of today's hardware design tools is that they all rely on the local machine's resources to do the hardest parts, which include geometry rendering and analysis. Our geometry engine parallelizes rendering and just sends video frames back to the app (seriously, inspect source, it's just a `<video>` element), and our API will offload analysis as we build it in
|
||||||
|
|
||||||
[Video](https://drive.google.com/file/d/183_wjqGdzZ8EEZXSqZ3eDcJocYPCyOdC/view?pli=1)
|
We are excited about what a small team of people could build in a short time with our API. We welcome you to try our API, build your own applications, or contribute to ours!
|
||||||
|
|
||||||
[demo-slides.pdf](https://github.com/KittyCAD/Eng/files/10398178/demo.pdf)
|
KittyCAD Modeling App is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more.
|
||||||
|
|
||||||
## To run, there are a couple steps since we're compiling rust to WASM, you'll need to have rust stuff installed, then
|
The 3D view in KittyCAD Modeling App is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
- UI
|
||||||
|
- [React](https://react.dev/)
|
||||||
|
- [Headless UI](https://headlessui.com/)
|
||||||
|
- [TailwindCSS](https://tailwindcss.com/)
|
||||||
|
- Networking
|
||||||
|
- WebSockets (via [KittyCAD TS client](https://github.com/KittyCAD/kittycad.ts))
|
||||||
|
- Code Editor
|
||||||
|
- [CodeMirror](https://codemirror.net/)
|
||||||
|
- Custom WASM LSP Server
|
||||||
|
- Modeling
|
||||||
|
- [KittyCAD TypeScript client](https://github.com/KittyCAD/kittycad.ts)
|
||||||
|
|
||||||
|
[Original demo video](https://drive.google.com/file/d/183_wjqGdzZ8EEZXSqZ3eDcJocYPCyOdC/view?pli=1)
|
||||||
|
|
||||||
|
[Original demo slides](https://github.com/KittyCAD/Eng/files/10398178/demo.pdf)
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
We recommend downloading the latest application binary from [our Releases page](https://github.com/KittyCAD/modeling-app/releases). If you don't see your platform or architecture supported there, please file an issue.
|
||||||
|
|
||||||
|
## Running a development build
|
||||||
|
|
||||||
|
First, [install Rust via `rustup`](https://www.rust-lang.org/tools/install). This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. Then, run:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn install
|
yarn install
|
||||||
```
|
```
|
||||||
then
|
|
||||||
|
followed by:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn build:wasm
|
yarn build:wasm
|
||||||
```
|
```
|
||||||
|
|
||||||
That will build the WASM binary and put in the `public` dir (though gitignored)
|
That will build the WASM binary and put in the `public` dir (though gitignored)
|
||||||
|
|
||||||
finally
|
finally, to run the web app only, run:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
and `yarn test` you would have need to have built the WASM previously. The tests need to download the binary from a server, so if you've already got `yarn start` running, that will work, otherwise running
|
|
||||||
```
|
|
||||||
yarn simpleserver
|
|
||||||
```
|
|
||||||
in one terminal
|
|
||||||
and
|
|
||||||
```
|
|
||||||
yarn test
|
|
||||||
```
|
|
||||||
in another.
|
|
||||||
|
|
||||||
If you want to edit the rust files, you can cd into `src/wasm-lib` and then use the usual rust commands, `cargo build`, `cargo test`, when you want to bring the changes back to the web-app, a fresh `yarn build:wasm` in the root will be needed.
|
|
||||||
|
|
||||||
Worth noting that the integration of the WASM into this project is very hacky because I'm really pushing create-react-app further than what's practical, but focusing on features atm rather than the setup.
|
|
||||||
|
|
||||||
## Developing in Chrome
|
## Developing in Chrome
|
||||||
|
|
||||||
Chrome is in the process of rolling out a new default which
|
Chrome is in the process of rolling out a new default which
|
||||||
@ -52,12 +76,26 @@ enable third-party cookies. You can enable third-party cookies by clicking on
|
|||||||
the eye with a slash through it in the URL bar, and clicking on "Enable
|
the eye with a slash through it in the URL bar, and clicking on "Enable
|
||||||
Third-Party Cookies".
|
Third-Party Cookies".
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
First, start the dev server following "Running a development build" above.
|
||||||
|
|
||||||
|
Then in another terminal tab, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn test
|
||||||
|
```
|
||||||
|
|
||||||
|
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
||||||
|
|
||||||
## Tauri
|
## Tauri
|
||||||
|
|
||||||
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
|
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn tauri dev
|
yarn tauri dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Will spin up the web app before opening up the tauri dev desktop app. Note that it's probably a good idea to close the browser tab that gets opened since at the time of writting they can conflict.
|
Will spin up the web app before opening up the tauri dev desktop app. Note that it's probably a good idea to close the browser tab that gets opened since at the time of writting they can conflict.
|
||||||
|
|
||||||
The dev instance automatically opens up the browser devtools which can be disabled by [commenting it out](https://github.com/KittyCAD/modeling-app/blob/main/src-tauri/src/main.rs#L92.)
|
The dev instance automatically opens up the browser devtools which can be disabled by [commenting it out](https://github.com/KittyCAD/modeling-app/blob/main/src-tauri/src/main.rs#L92.)
|
||||||
@ -67,19 +105,42 @@ To build, run `yarn tauri build`, or `yarn tauri build --debug` to keep access t
|
|||||||
Note that these became separate apps on Macos, so make sure you open the right one after a build 😉
|
Note that these became separate apps on Macos, so make sure you open the right one after a build 😉
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
<img width="1232" alt="image" src="https://user-images.githubusercontent.com/29681384/211947063-46164bb4-7bdd-45cb-9a76-2f40c71a24aa.png">
|
<img width="1232" alt="image" src="https://user-images.githubusercontent.com/29681384/211947063-46164bb4-7bdd-45cb-9a76-2f40c71a24aa.png">
|
||||||
|
|
||||||
<img width="1232" alt="image (1)" src="https://user-images.githubusercontent.com/29681384/211947073-e76b4933-bef5-4636-bc4d-e930ac8e290f.png">
|
<img width="1232" alt="image (1)" src="https://user-images.githubusercontent.com/29681384/211947073-e76b4933-bef5-4636-bc4d-e930ac8e290f.png">
|
||||||
|
|
||||||
|
## Before submitting a PR
|
||||||
|
|
||||||
|
Before you submit a contribution PR to this repo, please ensure that:
|
||||||
|
|
||||||
|
- There is a corresponding issue for the changes you want to make, so that discussion of approach can be had before work begins.
|
||||||
|
- You have separated out refactoring commits from feature commits as much as possible
|
||||||
|
- You have run all of the following commands locally:
|
||||||
|
- `yarn fmt`
|
||||||
|
- `yarn tsc`
|
||||||
|
- `yarn test`
|
||||||
|
- Here they are all together: `yarn fmt && yarn tsc && yarn test`
|
||||||
|
|
||||||
## Release a new version
|
## Release a new version
|
||||||
|
|
||||||
1. Bump the versions in the .json files by creating a `Bump to v{x}.{y}.{z}` PR, committing the changes from
|
1. Bump the versions in the .json files by creating a `Cut release v{x}.{y}.{z}` PR, committing the changes from
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
VERSION=x.y.z yarn run bump-jsons
|
VERSION=x.y.z yarn run bump-jsons
|
||||||
```
|
```
|
||||||
The PR may serve as a place to discuss the human-readable changelog and extra QA.
|
|
||||||
|
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and past in the following
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
console.log(
|
||||||
|
'- ' +
|
||||||
|
Array.from(
|
||||||
|
document.querySelectorAll('[data-hovercard-type="pull_request"]')
|
||||||
|
).map((a) => `[${a.innerText}](${a.href})`).join(`
|
||||||
|
- `)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
grab the md list and delete any that are older than the last bump
|
||||||
|
|
||||||
2. Merge the PR
|
2. Merge the PR
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
75
docs/kcl/types.md
Normal file
75
docs/kcl/types.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Types
|
||||||
|
|
||||||
|
`KCL` defines the following types and keywords the language.
|
||||||
|
|
||||||
|
All these types can be nested in various forms where nesting applies. Like
|
||||||
|
arrays can hold objects and vice versa.
|
||||||
|
|
||||||
|
## Boolean
|
||||||
|
|
||||||
|
`true` or `false` work when defining values.
|
||||||
|
|
||||||
|
## Variable declaration
|
||||||
|
|
||||||
|
Variables are defined with the `let` keyword like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myBool = false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Array
|
||||||
|
|
||||||
|
An array is defined with `[]` braces. What is inside the brackets can
|
||||||
|
be of any type. For example, the following is completely valid:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myArray = ["thing", 2, false]
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to get a value from an array you can use the index like so:
|
||||||
|
`myArray[0]`.
|
||||||
|
|
||||||
|
|
||||||
|
## Object
|
||||||
|
|
||||||
|
An object is defined with `{}` braces. Here is an example object:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myObj = {a: 0, b: "thing"}
|
||||||
|
```
|
||||||
|
|
||||||
|
We support two different ways of getting properties from objects, you can call
|
||||||
|
`myObj.a` or `myObj["a"]` both work.
|
||||||
|
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
We also have support for defining your own functions. Functions can take in any
|
||||||
|
type of argument. Below is an example of the syntax:
|
||||||
|
|
||||||
|
```
|
||||||
|
fn myFn = (x) => {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see above `myFn` just returns whatever it is given.
|
||||||
|
|
||||||
|
|
||||||
|
## Binary expressions
|
||||||
|
|
||||||
|
You can also do math! Let's show an example below:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myMathExpression = 3 + 1 * 2 / 3 - 7
|
||||||
|
```
|
||||||
|
|
||||||
|
You can nest expressions in parenthesis as well:
|
||||||
|
|
||||||
|
```
|
||||||
|
let myMathExpression = 3 + (1 * 2 / (3 - 7))
|
||||||
|
```
|
||||||
|
|
||||||
|
Please if you find any issues using any of the above expressions or syntax
|
||||||
|
please file an issue with the `ast` label on the [modeling-app
|
||||||
|
repo](https://github.com/KittyCAD/modeling-app/issues/new).
|
24
package.json
24
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.6.1",
|
"version": "0.10.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.9.0",
|
"@codemirror/autocomplete": "^6.9.0",
|
||||||
@ -10,10 +10,11 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "^1.7.13",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.37",
|
"@kittycad/lib": "^0.0.43",
|
||||||
"@lezer/javascript": "^1.4.7",
|
"@lezer/javascript": "^1.4.7",
|
||||||
"@open-rpc/client-js": "^1.8.1",
|
"@open-rpc/client-js": "^1.8.1",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
|
"@replit/codemirror-interact": "^6.3.0",
|
||||||
"@sentry/react": "^7.65.0",
|
"@sentry/react": "^7.65.0",
|
||||||
"@tauri-apps/api": "^1.3.0",
|
"@tauri-apps/api": "^1.3.0",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
@ -26,11 +27,12 @@
|
|||||||
"@uiw/react-codemirror": "^4.21.13",
|
"@uiw/react-codemirror": "^4.21.13",
|
||||||
"@xstate/react": "^3.2.2",
|
"@xstate/react": "^3.2.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
|
"debounce-promise": "^3.1.2",
|
||||||
"formik": "^2.4.3",
|
"formik": "^2.4.3",
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"json-rpc-2.0": "^1.6.0",
|
"json-rpc-2.0": "^1.6.0",
|
||||||
"re-resizable": "^6.9.9",
|
"re-resizable": "^6.9.11",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
@ -46,7 +48,7 @@
|
|||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^4.4.2",
|
"typescript": "^4.4.2",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vitest": "^0.34.1",
|
"vitest": "^0.34.6",
|
||||||
"vscode-jsonrpc": "^8.1.0",
|
"vscode-jsonrpc": "^8.1.0",
|
||||||
"vscode-languageserver-protocol": "^3.17.3",
|
"vscode-languageserver-protocol": "^3.17.3",
|
||||||
"wasm-pack": "^0.12.1",
|
"wasm-pack": "^0.12.1",
|
||||||
@ -61,15 +63,17 @@
|
|||||||
"build:local": "vite build",
|
"build:local": "vite build",
|
||||||
"build:both": "vite build",
|
"build:both": "vite build",
|
||||||
"build:both:local": "yarn build:wasm && vite build",
|
"build:both:local": "yarn build:wasm && vite build",
|
||||||
|
"pretest": "yarn remove-importmeta",
|
||||||
"test": "vitest --mode development",
|
"test": "vitest --mode development",
|
||||||
"test:nowatch": "vitest run --mode development",
|
"test:nowatch": "vitest run --mode development",
|
||||||
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests)",
|
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
|
||||||
"test:cov": "vitest run --coverage --mode development",
|
"test:cov": "vitest run --coverage --mode development",
|
||||||
"simpleserver:ci": "http-server ./public --cors -p 3000 &",
|
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||||
"simpleserver": "http-server ./public --cors -p 3000",
|
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||||
"fmt": "prettier --write ./src",
|
"fmt": "prettier --write ./src",
|
||||||
"fmt-check": "prettier --check ./src",
|
"fmt-check": "prettier --check ./src",
|
||||||
"build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta",
|
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
|
||||||
|
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||||
"lint": "eslint --fix src",
|
"lint": "eslint --fix src",
|
||||||
@ -98,7 +102,7 @@
|
|||||||
"@babel/preset-env": "^7.22.9",
|
"@babel/preset-env": "^7.22.9",
|
||||||
"@tauri-apps/cli": "^1.3.1",
|
"@tauri-apps/cli": "^1.3.1",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/debounce": "^1.2.1",
|
"@types/debounce-promise": "^3.1.6",
|
||||||
"@types/isomorphic-fetch": "^0.0.36",
|
"@types/isomorphic-fetch": "^0.0.36",
|
||||||
"@types/react-modal": "^3.16.0",
|
"@types/react-modal": "^3.16.0",
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
@ -112,7 +116,7 @@
|
|||||||
"eslint-plugin-css-modules": "^2.11.0",
|
"eslint-plugin-css-modules": "^2.11.0",
|
||||||
"happy-dom": "^10.8.0",
|
"happy-dom": "^10.8.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"postcss": "^8.4.19",
|
"postcss": "^8.4.31",
|
||||||
"prettier": "^2.8.0",
|
"prettier": "^2.8.0",
|
||||||
"setimmediate": "^1.0.5",
|
"setimmediate": "^1.0.5",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.2.4",
|
||||||
|
42
public/expectations.md
Normal file
42
public/expectations.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
## Alpha Users Expectations
|
||||||
|
|
||||||
|
### Welcome
|
||||||
|
|
||||||
|
First off, thank you so much for your interest in being a part of the closed Alpha program! We are thrilled to have others use our product and see what you build with it (and truthfully, how you break it too).
|
||||||
|
|
||||||
|
### KittyCAD Modeling App (KCMA)
|
||||||
|
|
||||||
|
What we are introducing to you is our KittyCAD Modeling App (KCMA). KCMA is a CAD application that expresses a hybrid style of traditional CAD interface along with a code-CAD interface. KCMA is a great way for us to test our own APIs as well as inspire others to develop their own applications.
|
||||||
|
|
||||||
|
### Why Code?
|
||||||
|
|
||||||
|
Plenty of you have professional CAD experience, and may not understand why coding your model would be helpful. The "code-CAD" paradigm isn’t as popular as traditional CAD programs (SolidWorks, NX, CREO, OnShape, etc.), but it certainly has its benefits. Some benefits include:
|
||||||
|
|
||||||
|
- Automation and parametric design
|
||||||
|
- Customization and flexibility
|
||||||
|
- Algorithmic and generative design
|
||||||
|
- Reproducibility
|
||||||
|
- Easier integration with other tools
|
||||||
|
|
||||||
|
### Before You Use KCMA
|
||||||
|
|
||||||
|
Before you dive straight into the app, we wanted to lay some expectations out for you.
|
||||||
|
|
||||||
|
- KCMA is in early development. Kurt pitched the idea back in January, and the team has been working hard on it since then. KCMA has really basic CAD features for now, but we have plenty of features on our roadmap. Most of the features that you may be currently used to in your CAD workflow today will be available down the road.
|
||||||
|
- For a list of all scripting functions, please reference our [documentation](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md). For a basic rundown of our types, please reference [this document](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/types.md).
|
||||||
|
- With that being said, we have created an external new features list in [GH Discussions](https://github.com/KittyCAD/modeling-app/discussions). For our current priority list, please click [here](https://github.com/KittyCAD/modeling-app/blob/main/public/roadmap.md). Please upvote any features in the GH Discussions page that you would like to see implemented first. We will prioritize the highest upvoted items or items that are foundational for other features on the list. You can also add your own, but we will review it to make sure it’s not a duplicate or it’s feasible for the current state of the app.
|
||||||
|
- Please report any and all bugs/issues you find. Even the smallest bugs are important! You can report them in a GH Issue [here](https://github.com/KittyCAD/modeling-app/issues/new). You are more than welcome to link your GH Issue in the **bugs** section of our Discord, but if you want to discuss the bug further, please keep that in the GH Issue thread. Please include the severity of the bug in your GH Issue ticket (High, Medium, or Low). If you are having trouble deciding what severity the bug is, use this guideline:
|
||||||
|
- **High:** The bug is blocking you from continuing.
|
||||||
|
- Example: Every time I click the extrude button with two faces selected, the app crashes.
|
||||||
|
- **Medium:** You can find a workaround to the problem, but it increases your time spent working or makes it unenjoyable.
|
||||||
|
- Example: When the app is full screen on Mac, the settings are not showing properly. It works if I have the app windowed.
|
||||||
|
- **Low:** The bug is annoying but doesn’t affect workflow or block you from continuing (usually you can say “It would be nice if ___, but it’s not needed”)
|
||||||
|
- Example: It would be nice if the camera would orient normal to the sketching surface when I select a face/plane and click “sketch”.
|
||||||
|
- We want you all to be aware that we may reach out to you in regard to issues, bugs, problems, and satisfaction. This will typically be for further clarification so we can really nail things down.
|
||||||
|
|
||||||
|
### Discord
|
||||||
|
We will be using Discord a lot more now that the Alpha has been released to people outside of the company. Please feel free to discuss and talk with us in the **alpha users** section of the server. We highly encourage you to engage with us on Discord!
|
||||||
|
|
||||||
|
### Thank You!
|
||||||
|
|
||||||
|
Once again, from all of us to you, thank you for being a part of the closed Alpha. We are happy to chat with you all, hear your feedback, and see some of your projects!
|
46
public/kcma-logomark-dark.svg
Normal file
46
public/kcma-logomark-dark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
BIN
public/kcma-logomark.png
Normal file
BIN
public/kcma-logomark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
46
public/kcma-logomark.svg
Normal file
46
public/kcma-logomark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
BIN
public/onboarding-bracket-dark.png
Normal file
BIN
public/onboarding-bracket-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 148 KiB |
BIN
public/onboarding-bracket.png
Normal file
BIN
public/onboarding-bracket.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
26
public/roadmap.md
Normal file
26
public/roadmap.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
## KittyCAD Modeling App Roadmap
|
||||||
|
|
||||||
|
This document ties into our [GH Discussions Feature List](https://github.com/KittyCAD/modeling-app/discussions). Please upvote any features that you want to see next, or add ones that are not listed and we will review.
|
||||||
|
|
||||||
|
### Current Priority List
|
||||||
|
|
||||||
|
1. [Sketch on Face](https://github.com/KittyCAD/modeling-app/discussions/477)
|
||||||
|
2. [Revolve](https://github.com/KittyCAD/modeling-app/discussions/496)
|
||||||
|
3. [Fillet](https://github.com/KittyCAD/modeling-app/discussions/501)
|
||||||
|
4. [Linear Pattern](https://github.com/KittyCAD/modeling-app/discussions/256)
|
||||||
|
5. [Circular Pattern](https://github.com/KittyCAD/modeling-app/discussions/257)
|
||||||
|
6. [Mirror-Sketch](https://github.com/KittyCAD/modeling-app/discussions/507)
|
||||||
|
7. [Chamfer](https://github.com/KittyCAD/modeling-app/discussions/502)
|
||||||
|
8. [Sweep](https://github.com/KittyCAD/modeling-app/discussions/498)
|
||||||
|
9. [Draft](https://github.com/KittyCAD/modeling-app/discussions/495)
|
||||||
|
10. [Shell](https://github.com/KittyCAD/modeling-app/discussions/503)
|
||||||
|
11. [Union](https://github.com/KittyCAD/modeling-app/discussions/509)
|
||||||
|
12. [Mirror-Model](https://github.com/KittyCAD/modeling-app/discussions/508)
|
||||||
|
13. [Subtract](https://github.com/KittyCAD/modeling-app/discussions/510)
|
||||||
|
14. [Intersect](https://github.com/KittyCAD/modeling-app/discussions/511)
|
||||||
|
15. [Offset](https://github.com/KittyCAD/modeling-app/discussions/512)
|
||||||
|
16. [Thicken](https://github.com/KittyCAD/modeling-app/discussions/499)
|
||||||
|
17. [Import](https://github.com/KittyCAD/modeling-app/discussions/478)
|
||||||
|
18. [Assemblies](https://github.com/KittyCAD/modeling-app/discussions/494)
|
||||||
|
19. [External Thread](https://github.com/KittyCAD/modeling-app/discussions/505)
|
||||||
|
|
1117
src-tauri/Cargo.lock
generated
1117
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -12,17 +12,18 @@ rust-version = "1.60"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "1.4.0", features = [] }
|
tauri-build = { version = "1.5.0", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
oauth2 = "4.4.1"
|
kittycad = "0.2.28"
|
||||||
|
oauth2 = "4.4.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tauri = { version = "1.3.0", features = [ "updater", "path-all", "dialog-all", "fs-all", "http-request", "shell-open", "shell-open-api"] }
|
tauri = { version = "1.5.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
|
||||||
tokio = { version = "1.29.1", features = ["time"] }
|
|
||||||
toml = "0.6.0"
|
|
||||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
|
tokio = { version = "1.32.0", features = ["time"] }
|
||||||
|
toml = "0.8.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
||||||
|
@ -6,6 +6,7 @@ use std::io::Read;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use oauth2::TokenResponse;
|
use oauth2::TokenResponse;
|
||||||
use tauri::{InvokeError, Manager};
|
use tauri::{InvokeError, Manager};
|
||||||
|
const DEFAULT_HOST: &str = "https://api.kittycad.io";
|
||||||
|
|
||||||
/// This command returns the a json string parse from a toml file at the path.
|
/// This command returns the a json string parse from a toml file at the path.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -85,6 +86,47 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
|
|||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///This command returns the KittyCAD user info given a token.
|
||||||
|
/// The string returned from this method is the user info as a json string.
|
||||||
|
#[tauri::command]
|
||||||
|
async fn get_user(
|
||||||
|
token: Option<String>,
|
||||||
|
hostname: &str,
|
||||||
|
) -> Result<kittycad::types::User, InvokeError> {
|
||||||
|
// Use the host passed in if it's set.
|
||||||
|
// Otherwise, use the default host.
|
||||||
|
let host = if hostname.is_empty() {
|
||||||
|
DEFAULT_HOST.to_string()
|
||||||
|
} else {
|
||||||
|
hostname.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Change the baseURL to the one we want.
|
||||||
|
let mut baseurl = host.to_string();
|
||||||
|
if !host.starts_with("http://") && !host.starts_with("https://") {
|
||||||
|
baseurl = format!("https://{host}");
|
||||||
|
if host.starts_with("localhost") {
|
||||||
|
baseurl = format!("http://{host}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Getting user info...");
|
||||||
|
|
||||||
|
// use kittycad library to fetch the user info from /user/me
|
||||||
|
let mut client = kittycad::Client::new(token.unwrap());
|
||||||
|
|
||||||
|
if baseurl != DEFAULT_HOST {
|
||||||
|
client.set_base_url(&baseurl);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_info: kittycad::types::User = client
|
||||||
|
.users()
|
||||||
|
.get_self()
|
||||||
|
.await
|
||||||
|
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||||
|
|
||||||
|
Ok(user_info)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
@ -97,7 +139,12 @@ fn main() {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![login, read_toml, read_txt_file])
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
get_user,
|
||||||
|
login,
|
||||||
|
read_toml,
|
||||||
|
read_txt_file
|
||||||
|
])
|
||||||
.plugin(tauri_plugin_fs_extra::init())
|
.plugin(tauri_plugin_fs_extra::init())
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "kittycad-modeling",
|
"productName": "kittycad-modeling",
|
||||||
"version": "0.6.1"
|
"version": "0.10.0"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
@ -36,6 +36,9 @@
|
|||||||
"https://api.dev.kittycad.io/*"
|
"https://api.dev.kittycad.io/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"os": {
|
||||||
|
"all": true
|
||||||
|
},
|
||||||
"shell": {
|
"shell": {
|
||||||
"open": true
|
"open": true
|
||||||
},
|
},
|
||||||
@ -71,9 +74,9 @@
|
|||||||
"shortDescription": "",
|
"shortDescription": "",
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
"windows": {
|
"windows": {
|
||||||
"certificateThumbprint": null,
|
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
|
||||||
"digestAlgorithm": "sha256",
|
"digestAlgorithm": "sha256",
|
||||||
"timestampUrl": ""
|
"timestampUrl": "http://timestamp.digicert.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import { App } from './App'
|
import { App } from './App'
|
||||||
import { describe, test, vi } from 'vitest'
|
import { describe, test, vi } from 'vitest'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import {
|
||||||
|
Route,
|
||||||
|
RouterProvider,
|
||||||
|
createMemoryRouter,
|
||||||
|
createRoutesFromElements,
|
||||||
|
} from 'react-router-dom'
|
||||||
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
||||||
import CommandBarProvider from 'components/CommandBar'
|
import CommandBarProvider from 'components/CommandBar'
|
||||||
|
import { BROWSER_FILE_NAME } from 'Router'
|
||||||
|
|
||||||
let listener: ((rect: any) => void) | undefined = undefined
|
let listener: ((rect: any) => void) | undefined = undefined
|
||||||
;(global as any).ResizeObserver = class ResizeObserver {
|
;(global as any).ResizeObserver = class ResizeObserver {
|
||||||
@ -24,7 +30,7 @@ describe('App tests', () => {
|
|||||||
>
|
>
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
useParams: () => ({ id: 'new' }),
|
useParams: () => ({ id: BROWSER_FILE_NAME }),
|
||||||
useLoaderData: () => ({ code: null }),
|
useLoaderData: () => ({ code: null }),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -41,12 +47,24 @@ describe('App tests', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function TestWrap({ children }: { children: React.ReactNode }) {
|
function TestWrap({ children }: { children: React.ReactNode }) {
|
||||||
// wrap in router and xState context
|
// We have to use a memory router in the testing environment,
|
||||||
return (
|
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
|
||||||
<BrowserRouter>
|
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
|
||||||
<CommandBarProvider>
|
const router = createMemoryRouter(
|
||||||
<GlobalStateProvider>{children}</GlobalStateProvider>
|
createRoutesFromElements(
|
||||||
</CommandBarProvider>
|
<Route
|
||||||
</BrowserRouter>
|
path="/file/:id"
|
||||||
|
element={
|
||||||
|
<CommandBarProvider>
|
||||||
|
<GlobalStateProvider>{children}</GlobalStateProvider>
|
||||||
|
</CommandBarProvider>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
initialEntries: ['/file/new'],
|
||||||
|
initialIndex: 0,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
return <RouterProvider router={router} />
|
||||||
}
|
}
|
||||||
|
262
src/App.tsx
262
src/App.tsx
@ -1,14 +1,6 @@
|
|||||||
import {
|
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
|
||||||
useRef,
|
|
||||||
useEffect,
|
|
||||||
useLayoutEffect,
|
|
||||||
useCallback,
|
|
||||||
MouseEventHandler,
|
|
||||||
} from 'react'
|
|
||||||
import { DebugPanel } from './components/DebugPanel'
|
import { DebugPanel } from './components/DebugPanel'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { asyncParser } from './lang/abstractSyntaxTree'
|
|
||||||
import { _executor } from './lang/executor'
|
|
||||||
import { PaneType, useStore } from './useStore'
|
import { PaneType, useStore } from './useStore'
|
||||||
import { Logs, KCLErrors } from './components/Logs'
|
import { Logs, KCLErrors } from './components/Logs'
|
||||||
import { CollapsiblePanel } from './components/CollapsiblePanel'
|
import { CollapsiblePanel } from './components/CollapsiblePanel'
|
||||||
@ -16,13 +8,9 @@ import { MemoryPanel } from './components/MemoryPanel'
|
|||||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||||
import { Stream } from './components/Stream'
|
import { Stream } from './components/Stream'
|
||||||
import ModalContainer from 'react-modal-promise'
|
import ModalContainer from 'react-modal-promise'
|
||||||
import {
|
import { EngineCommand } from './lang/std/engineConnection'
|
||||||
EngineCommand,
|
|
||||||
EngineCommandManager,
|
|
||||||
} from './lang/std/engineConnection'
|
|
||||||
import { throttle } from './lib/utils'
|
import { throttle } from './lib/utils'
|
||||||
import { AppHeader } from './components/AppHeader'
|
import { AppHeader } from './components/AppHeader'
|
||||||
import { KCLError } from './lang/errors'
|
|
||||||
import { Resizable } from 're-resizable'
|
import { Resizable } from 're-resizable'
|
||||||
import {
|
import {
|
||||||
faCode,
|
faCode,
|
||||||
@ -41,6 +29,9 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
|
|||||||
import { CodeMenu } from 'components/CodeMenu'
|
import { CodeMenu } from 'components/CodeMenu'
|
||||||
import { TextEditor } from 'components/TextEditor'
|
import { TextEditor } from 'components/TextEditor'
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
|
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||||
|
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||||
|
import { engineCommandManager } from './lang/std/engineConnection'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
||||||
@ -48,59 +39,25 @@ export function App() {
|
|||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
const {
|
const {
|
||||||
addLog,
|
|
||||||
addKCLError,
|
|
||||||
setCode,
|
setCode,
|
||||||
setAst,
|
|
||||||
setError,
|
|
||||||
setProgramMemory,
|
|
||||||
resetLogs,
|
|
||||||
resetKCLErrors,
|
|
||||||
setArtifactMap,
|
|
||||||
engineCommandManager,
|
|
||||||
setEngineCommandManager,
|
|
||||||
highlightRange,
|
|
||||||
setHighlightRange,
|
|
||||||
setCursor2,
|
|
||||||
setMediaStream,
|
|
||||||
setIsStreamReady,
|
|
||||||
isStreamReady,
|
|
||||||
buttonDownInStream,
|
buttonDownInStream,
|
||||||
openPanes,
|
openPanes,
|
||||||
setOpenPanes,
|
setOpenPanes,
|
||||||
didDragInStream,
|
didDragInStream,
|
||||||
setStreamDimensions,
|
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
setIsExecuting,
|
|
||||||
defferedCode,
|
|
||||||
guiMode,
|
guiMode,
|
||||||
|
setGuiMode,
|
||||||
|
executeAst,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
addLog: s.addLog,
|
setGuiMode: s.setGuiMode,
|
||||||
defferedCode: s.defferedCode,
|
|
||||||
setCode: s.setCode,
|
setCode: s.setCode,
|
||||||
setAst: s.setAst,
|
|
||||||
setError: s.setError,
|
|
||||||
setProgramMemory: s.setProgramMemory,
|
|
||||||
resetLogs: s.resetLogs,
|
|
||||||
resetKCLErrors: s.resetKCLErrors,
|
|
||||||
setArtifactMap: s.setArtifactNSourceRangeMaps,
|
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
setEngineCommandManager: s.setEngineCommandManager,
|
|
||||||
highlightRange: s.highlightRange,
|
|
||||||
setHighlightRange: s.setHighlightRange,
|
|
||||||
setCursor2: s.setCursor2,
|
|
||||||
setMediaStream: s.setMediaStream,
|
|
||||||
isStreamReady: s.isStreamReady,
|
|
||||||
setIsStreamReady: s.setIsStreamReady,
|
|
||||||
buttonDownInStream: s.buttonDownInStream,
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
addKCLError: s.addKCLError,
|
|
||||||
openPanes: s.openPanes,
|
openPanes: s.openPanes,
|
||||||
setOpenPanes: s.setOpenPanes,
|
setOpenPanes: s.setOpenPanes,
|
||||||
didDragInStream: s.didDragInStream,
|
didDragInStream: s.didDragInStream,
|
||||||
setStreamDimensions: s.setStreamDimensions,
|
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
setIsExecuting: s.setIsExecuting,
|
executeAst: s.executeAst,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -127,13 +84,58 @@ export function App() {
|
|||||||
useHotkeys('shift + l', () => togglePane('logs'))
|
useHotkeys('shift + l', () => togglePane('logs'))
|
||||||
useHotkeys('shift + e', () => togglePane('kclErrors'))
|
useHotkeys('shift + e', () => togglePane('kclErrors'))
|
||||||
useHotkeys('shift + d', () => togglePane('debug'))
|
useHotkeys('shift + d', () => togglePane('debug'))
|
||||||
|
useHotkeys('esc', () => {
|
||||||
|
if (guiMode.mode === 'sketch') {
|
||||||
|
if (guiMode.sketchMode === 'selectFace') return
|
||||||
|
if (guiMode.sketchMode === 'sketchEdit') {
|
||||||
|
// TODO: share this with Toolbar's "Exit sketch" button
|
||||||
|
// exiting sketch should be done consistently across all exits
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'edit_mode_exit' },
|
||||||
|
})
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
|
})
|
||||||
|
setGuiMode({ mode: 'default' })
|
||||||
|
// this is necessary to get the UI back into a consistent
|
||||||
|
// state right now, hopefully won't need to rerender
|
||||||
|
// when exiting sketch mode in the future
|
||||||
|
executeAst()
|
||||||
|
} else {
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'set_tool',
|
||||||
|
tool: 'select',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setGuiMode({
|
||||||
|
mode: 'sketch',
|
||||||
|
sketchMode: 'sketchEdit',
|
||||||
|
rotation: guiMode.rotation,
|
||||||
|
position: guiMode.position,
|
||||||
|
pathToNode: guiMode.pathToNode,
|
||||||
|
pathId: guiMode.pathId,
|
||||||
|
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setGuiMode({ mode: 'default' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const paneOpacity =
|
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
||||||
onboardingStatus === onboardingPaths.CAMERA
|
(p) => p === onboardingStatus
|
||||||
? 'opacity-20'
|
)
|
||||||
: didDragInStream
|
? 'opacity-20'
|
||||||
? 'opacity-40'
|
: didDragInStream
|
||||||
: ''
|
? 'opacity-40'
|
||||||
|
: ''
|
||||||
|
|
||||||
// Use file code loaded from disk
|
// Use file code loaded from disk
|
||||||
// on mount, and overwrite any locally-stored code
|
// on mount, and overwrite any locally-stored code
|
||||||
@ -149,146 +151,11 @@ export function App() {
|
|||||||
}
|
}
|
||||||
}, [loadedCode, setCode])
|
}, [loadedCode, setCode])
|
||||||
|
|
||||||
const streamWidth = streamRef?.current?.offsetWidth
|
useSetupEngineManager(streamRef, token)
|
||||||
const streamHeight = streamRef?.current?.offsetHeight
|
useEngineConnectionSubscriptions()
|
||||||
|
|
||||||
const width = streamWidth ? streamWidth : 0
|
|
||||||
const quadWidth = Math.round(width / 4) * 4
|
|
||||||
const height = streamHeight ? streamHeight : 0
|
|
||||||
const quadHeight = Math.round(height / 4) * 4
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
setStreamDimensions({
|
|
||||||
streamWidth: quadWidth,
|
|
||||||
streamHeight: quadHeight,
|
|
||||||
})
|
|
||||||
if (!width || !height) return
|
|
||||||
const eng = new EngineCommandManager({
|
|
||||||
setMediaStream,
|
|
||||||
setIsStreamReady,
|
|
||||||
width: quadWidth,
|
|
||||||
height: quadHeight,
|
|
||||||
token,
|
|
||||||
})
|
|
||||||
setEngineCommandManager(eng)
|
|
||||||
return () => {
|
|
||||||
eng?.tearDown()
|
|
||||||
}
|
|
||||||
}, [quadWidth, quadHeight])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isStreamReady) return
|
|
||||||
if (!engineCommandManager) return
|
|
||||||
let unsubFn: any[] = []
|
|
||||||
const asyncWrap = async () => {
|
|
||||||
try {
|
|
||||||
if (!defferedCode) {
|
|
||||||
setAst({
|
|
||||||
start: 0,
|
|
||||||
end: 0,
|
|
||||||
body: [],
|
|
||||||
nonCodeMeta: {
|
|
||||||
noneCodeNodes: {},
|
|
||||||
start: null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setProgramMemory({ root: {}, return: null })
|
|
||||||
engineCommandManager.endSession()
|
|
||||||
engineCommandManager.startNewSession()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const _ast = await asyncParser(defferedCode)
|
|
||||||
setAst(_ast)
|
|
||||||
resetLogs()
|
|
||||||
resetKCLErrors()
|
|
||||||
engineCommandManager.endSession()
|
|
||||||
engineCommandManager.startNewSession()
|
|
||||||
setIsExecuting(true)
|
|
||||||
const programMemory = await _executor(
|
|
||||||
_ast,
|
|
||||||
{
|
|
||||||
root: {
|
|
||||||
_0: {
|
|
||||||
type: 'UserVal',
|
|
||||||
value: 0,
|
|
||||||
__meta: [],
|
|
||||||
},
|
|
||||||
_90: {
|
|
||||||
type: 'UserVal',
|
|
||||||
value: 90,
|
|
||||||
__meta: [],
|
|
||||||
},
|
|
||||||
_180: {
|
|
||||||
type: 'UserVal',
|
|
||||||
value: 180,
|
|
||||||
__meta: [],
|
|
||||||
},
|
|
||||||
_270: {
|
|
||||||
type: 'UserVal',
|
|
||||||
value: 270,
|
|
||||||
__meta: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
return: null,
|
|
||||||
},
|
|
||||||
engineCommandManager
|
|
||||||
)
|
|
||||||
|
|
||||||
const { artifactMap, sourceRangeMap } =
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
|
||||||
setIsExecuting(false)
|
|
||||||
if (programMemory !== undefined) {
|
|
||||||
setProgramMemory(programMemory)
|
|
||||||
}
|
|
||||||
|
|
||||||
setArtifactMap({ artifactMap, sourceRangeMap })
|
|
||||||
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
|
||||||
event: 'highlight_set_entity',
|
|
||||||
callback: ({ data }) => {
|
|
||||||
if (data?.entity_id) {
|
|
||||||
const sourceRange = sourceRangeMap[data.entity_id]
|
|
||||||
setHighlightRange(sourceRange)
|
|
||||||
} else if (
|
|
||||||
!highlightRange ||
|
|
||||||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
|
|
||||||
) {
|
|
||||||
setHighlightRange([0, 0])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const unSubClick = engineCommandManager.subscribeTo({
|
|
||||||
event: 'select_with_point',
|
|
||||||
callback: ({ data }) => {
|
|
||||||
if (!data?.entity_id) {
|
|
||||||
setCursor2()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const sourceRange = sourceRangeMap[data.entity_id]
|
|
||||||
setCursor2({ range: sourceRange, type: 'default' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
unsubFn.push(unSubHover, unSubClick)
|
|
||||||
|
|
||||||
setError()
|
|
||||||
} catch (e: any) {
|
|
||||||
setIsExecuting(false)
|
|
||||||
if (e instanceof KCLError) {
|
|
||||||
addKCLError(e)
|
|
||||||
} else {
|
|
||||||
setError('problem')
|
|
||||||
console.log(e)
|
|
||||||
addLog(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
asyncWrap()
|
|
||||||
return () => {
|
|
||||||
unsubFn.forEach((fn) => fn())
|
|
||||||
}
|
|
||||||
}, [defferedCode, isStreamReady, engineCommandManager])
|
|
||||||
|
|
||||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||||
engineCommandManager?.sendSceneCommand(message)
|
engineCommandManager.sendSceneCommand(message)
|
||||||
}, 16)
|
}, 16)
|
||||||
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
e.nativeEvent.preventDefault()
|
e.nativeEvent.preventDefault()
|
||||||
@ -348,7 +215,6 @@ export function App() {
|
|||||||
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
||||||
interaction = 'zoom'
|
interaction = 'zoom'
|
||||||
} else {
|
} else {
|
||||||
console.log('none')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,7 +267,7 @@ export function App() {
|
|||||||
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
|
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="h-full flex flex-col justify-between">
|
<div id="code-pane" className="h-full flex flex-col justify-between">
|
||||||
<CollapsiblePanel
|
<CollapsiblePanel
|
||||||
title="Code"
|
title="Code"
|
||||||
icon={faCode}
|
icon={faCode}
|
||||||
|
@ -6,9 +6,9 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
|
|||||||
const {
|
const {
|
||||||
auth: { state },
|
auth: { state },
|
||||||
} = useGlobalStateContext()
|
} = useGlobalStateContext()
|
||||||
const isLoggedIn = state.matches('checkIfLoggedIn')
|
const isLoggingIn = state.matches('checkIfLoggedIn')
|
||||||
|
|
||||||
return isLoggedIn ? (
|
return isLoggingIn ? (
|
||||||
<Loading>Loading KittyCAD Modeling App...</Loading>
|
<Loading>Loading KittyCAD Modeling App...</Loading>
|
||||||
) : (
|
) : (
|
||||||
<>{children}</>
|
<>{children}</>
|
||||||
|
@ -94,6 +94,8 @@ export const paths = {
|
|||||||
) as typeof onboardingPaths,
|
) as typeof onboardingPaths,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BROWSER_FILE_NAME = 'new'
|
||||||
|
|
||||||
export type IndexLoaderData = {
|
export type IndexLoaderData = {
|
||||||
code: string | null
|
code: string | null
|
||||||
project?: ProjectWithEntryPointMetadata
|
project?: ProjectWithEntryPointMetadata
|
||||||
@ -129,7 +131,10 @@ const router = createBrowserRouter(
|
|||||||
{
|
{
|
||||||
path: paths.INDEX,
|
path: paths.INDEX,
|
||||||
loader: () =>
|
loader: () =>
|
||||||
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
|
isTauri()
|
||||||
|
? redirect(paths.HOME)
|
||||||
|
: redirect(paths.FILE + '/' + BROWSER_FILE_NAME),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: paths.FILE + '/:id',
|
path: paths.FILE + '/:id',
|
||||||
@ -140,7 +145,6 @@ const router = createBrowserRouter(
|
|||||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||||
</Auth>
|
</Auth>
|
||||||
),
|
),
|
||||||
errorElement: <ErrorPage />,
|
|
||||||
id: paths.FILE,
|
id: paths.FILE,
|
||||||
loader: async ({
|
loader: async ({
|
||||||
request,
|
request,
|
||||||
@ -167,7 +171,7 @@ const router = createBrowserRouter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.id && params.id !== 'new') {
|
if (params.id && params.id !== BROWSER_FILE_NAME) {
|
||||||
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
||||||
const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
|
const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
|
||||||
const entrypoint_metadata = await metadata(
|
const entrypoint_metadata = await metadata(
|
||||||
@ -212,7 +216,7 @@ const router = createBrowserRouter(
|
|||||||
),
|
),
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
if (!isTauri()) {
|
if (!isTauri()) {
|
||||||
return redirect(paths.FILE + '/new')
|
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
|
||||||
}
|
}
|
||||||
const fetchedStorage = localStorage?.getItem(SETTINGS_PERSIST_KEY)
|
const fetchedStorage = localStorage?.getItem(SETTINGS_PERSIST_KEY)
|
||||||
const persistedSettings = JSON.parse(fetchedStorage || '{}') as Partial<
|
const persistedSettings = JSON.parse(fetchedStorage || '{}') as Partial<
|
||||||
|
@ -47,6 +47,52 @@
|
|||||||
@apply hover:bg-cool-20;
|
@apply hover:bg-cool-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbarButtons::-webkit-scrollbar {
|
||||||
|
@apply h-0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButtons {
|
||||||
|
@apply flex items-center overflow-x-auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButtons button {
|
||||||
|
@apply text-chalkboard-90 bg-chalkboard-10/50 border-chalkboard-50 whitespace-nowrap;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
@apply gap-1.5 p-0.5 pr-1;
|
||||||
|
@apply rounded-sm;
|
||||||
|
}
|
||||||
|
:global(.dark) .toolbarButtons button {
|
||||||
|
@apply text-chalkboard-30 bg-chalkboard-90/50 border-chalkboard-50;
|
||||||
|
}
|
||||||
|
.toolbarButtons button:hover {
|
||||||
|
@apply text-cool-90 bg-cool-10;
|
||||||
|
}
|
||||||
|
:global(.sketch) .toolbarButtons button:hover {
|
||||||
|
@apply text-fern-90 bg-fern-10;
|
||||||
|
}
|
||||||
|
.toolbarButtons button:disabled {
|
||||||
|
@apply text-chalkboard-70 bg-chalkboard-30;
|
||||||
|
}
|
||||||
|
.toolbarButtons button:disabled:hover {
|
||||||
|
@apply !bg-inherit !text-inherit cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .toolbarButtons button {
|
||||||
|
@apply text-chalkboard-20 border-chalkboard-50;
|
||||||
|
}
|
||||||
|
:global(.dark) .toolbarButtons button:hover {
|
||||||
|
@apply text-cool-10 border-chalkboard-50 bg-cool-90;
|
||||||
|
}
|
||||||
|
:global(.dark .sketch) .toolbarButtons button:hover {
|
||||||
|
@apply text-fern-10 border-chalkboard-50 bg-fern-90;
|
||||||
|
}
|
||||||
|
:global(.dark) .toolbarButtons button:disabled {
|
||||||
|
@apply text-chalkboard-40 bg-chalkboard-80;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.dark) .popoverToggle {
|
:global(.dark) .popoverToggle {
|
||||||
@apply hover:bg-cool-90;
|
@apply hover:bg-cool-90;
|
||||||
}
|
}
|
||||||
|
137
src/Toolbar.tsx
137
src/Toolbar.tsx
@ -1,4 +1,4 @@
|
|||||||
import { useStore, toolTips, Selections } from './useStore'
|
import { useStore, toolTips, ToolTip } from './useStore'
|
||||||
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
||||||
import { getNodePathFromSourceRange } from './lang/queryAst'
|
import { getNodePathFromSourceRange } from './lang/queryAst'
|
||||||
import { HorzVert } from './components/Toolbar/HorzVert'
|
import { HorzVert } from './components/Toolbar/HorzVert'
|
||||||
@ -10,13 +10,38 @@ import { SetHorzVertDistance } from './components/Toolbar/SetHorzVertDistance'
|
|||||||
import { SetAngleLength } from './components/Toolbar/setAngleLength'
|
import { SetAngleLength } from './components/Toolbar/setAngleLength'
|
||||||
import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
|
import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
|
||||||
import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
|
import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
|
||||||
import { Fragment, useEffect } from 'react'
|
import { Fragment, WheelEvent, useRef } from 'react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
|
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Popover, Transition } from '@headlessui/react'
|
import { Popover, Transition } from '@headlessui/react'
|
||||||
import styles from './Toolbar.module.css'
|
import styles from './Toolbar.module.css'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useAppMode } from 'hooks/useAppMode'
|
import { useAppMode } from 'hooks/useAppMode'
|
||||||
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { engineCommandManager } from './lang/std/engineConnection'
|
||||||
|
|
||||||
|
export const sketchButtonClassnames = {
|
||||||
|
background:
|
||||||
|
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-fern-20 dark:group-hover:bg-fern-10 dark:hover:bg-fern-10 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50',
|
||||||
|
icon: 'text-fern-20 h-auto group-hover:text-fern-10 hover:text-fern-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-60 hover:group-disabled:text-inherit',
|
||||||
|
}
|
||||||
|
|
||||||
|
const sketchFnLabels: Record<ToolTip | 'sketch_line' | 'move', string> = {
|
||||||
|
sketch_line: 'Line',
|
||||||
|
line: 'Line',
|
||||||
|
move: 'Move',
|
||||||
|
angledLine: 'Angled Line',
|
||||||
|
angledLineThatIntersects: 'Angled Line That Intersects',
|
||||||
|
angledLineOfXLength: 'Angled Line Of X Length',
|
||||||
|
angledLineOfYLength: 'Angled Line Of Y Length',
|
||||||
|
angledLineToX: 'Angled Line To X',
|
||||||
|
angledLineToY: 'Angled Line To Y',
|
||||||
|
lineTo: 'Line to Point',
|
||||||
|
xLine: 'Horizontal Line',
|
||||||
|
yLine: 'Vertical Line',
|
||||||
|
xLineTo: 'Horizontal Line to Point',
|
||||||
|
yLineTo: 'Vertical Line to Point',
|
||||||
|
}
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const {
|
const {
|
||||||
@ -26,7 +51,7 @@ export const Toolbar = () => {
|
|||||||
ast,
|
ast,
|
||||||
updateAst,
|
updateAst,
|
||||||
programMemory,
|
programMemory,
|
||||||
engineCommandManager,
|
executeAst,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
setGuiMode: s.setGuiMode,
|
setGuiMode: s.setGuiMode,
|
||||||
@ -34,17 +59,27 @@ export const Toolbar = () => {
|
|||||||
ast: s.ast,
|
ast: s.ast,
|
||||||
updateAst: s.updateAst,
|
updateAst: s.updateAst,
|
||||||
programMemory: s.programMemory,
|
programMemory: s.programMemory,
|
||||||
engineCommandManager: s.engineCommandManager,
|
executeAst: s.executeAst,
|
||||||
}))
|
}))
|
||||||
useAppMode()
|
useAppMode()
|
||||||
|
const toolbarButtonsRef = useRef<HTMLSpanElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
||||||
console.log('guiMode', guiMode)
|
const span = toolbarButtonsRef.current
|
||||||
}, [guiMode])
|
if (!span) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
function ToolbarButtons() {
|
span.scrollLeft = span.scrollLeft += ev.deltaY
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||||
return (
|
return (
|
||||||
<span className="overflow-x-auto">
|
<span
|
||||||
|
ref={toolbarButtonsRef}
|
||||||
|
onWheel={handleToolbarButtonsWheelEvent}
|
||||||
|
className={styles.toolbarButtons + ' ' + className}
|
||||||
|
>
|
||||||
{guiMode.mode === 'default' && (
|
{guiMode.mode === 'default' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -53,7 +88,9 @@ export const Toolbar = () => {
|
|||||||
sketchMode: 'selectFace',
|
sketchMode: 'selectFace',
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||||
Start Sketch
|
Start Sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -70,33 +107,33 @@ export const Toolbar = () => {
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
programMemory
|
programMemory
|
||||||
)
|
)
|
||||||
updateAst(modifiedAst)
|
updateAst(modifiedAst, true)
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
SketchOnFace
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||||
|
Sketch on Face
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{guiMode.mode === 'canEditSketch' && (
|
{guiMode.mode === 'canEditSketch' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log('guiMode.pathId', guiMode.pathId)
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
engineCommandManager?.sendSceneCommand({
|
ast,
|
||||||
type: 'modeling_cmd_req',
|
selectionRanges.codeBasedSelections[0].range
|
||||||
cmd_id: uuidv4(),
|
)
|
||||||
cmd: {
|
|
||||||
type: 'edit_mode_enter',
|
|
||||||
target: guiMode.pathId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'enterSketchEdit',
|
||||||
pathToNode: guiMode.pathToNode,
|
pathToNode: pathToNode,
|
||||||
rotation: guiMode.rotation,
|
rotation: [0, 0, 0, 1],
|
||||||
position: guiMode.position,
|
position: [0, 0, 0],
|
||||||
|
pathId: guiMode.pathId,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||||
Edit Sketch
|
Edit Sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -113,10 +150,12 @@ export const Toolbar = () => {
|
|||||||
ast,
|
ast,
|
||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
ExtrudeSketch
|
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
||||||
|
Extrude
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -130,10 +169,12 @@ export const Toolbar = () => {
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
ExtrudeSketch (w/o pipe)
|
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
||||||
|
Extrude as new
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -141,14 +182,29 @@ export const Toolbar = () => {
|
|||||||
{guiMode.mode === 'sketch' && (
|
{guiMode.mode === 'sketch' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: { type: 'edit_mode_exit' },
|
cmd: { type: 'edit_mode_exit' },
|
||||||
})
|
})
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
|
})
|
||||||
|
|
||||||
setGuiMode({ mode: 'default' })
|
setGuiMode({ mode: 'default' })
|
||||||
|
executeAst()
|
||||||
}}
|
}}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
|
<ActionIcon
|
||||||
|
icon="exit"
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
Exit sketch
|
Exit sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -167,7 +223,7 @@ export const Toolbar = () => {
|
|||||||
<button
|
<button
|
||||||
key={sketchFnName}
|
key={sketchFnName}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -189,12 +245,25 @@ export const Toolbar = () => {
|
|||||||
sketchMode: sketchFnName,
|
sketchMode: sketchFnName,
|
||||||
waitingFirstClick: true,
|
waitingFirstClick: true,
|
||||||
isTooltip: true,
|
isTooltip: true,
|
||||||
|
pathId: guiMode.pathId,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
className={
|
||||||
|
'group ' +
|
||||||
|
(guiMode.sketchMode === sketchFnName
|
||||||
|
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
|
||||||
|
: '')
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{sketchFnName}
|
<ActionIcon
|
||||||
{guiMode.sketchMode === sketchFnName && '✅'}
|
icon={sketchFnName.includes('line') ? 'line' : 'move'}
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
{sketchFnLabels[sketchFnName]}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -225,7 +294,7 @@ export const Toolbar = () => {
|
|||||||
<span className={styles.toolbarCap + ' ' + styles.label}>
|
<span className={styles.toolbarCap + ' ' + styles.label}>
|
||||||
{guiMode.mode === 'sketch' ? '2D' : '3D'}
|
{guiMode.mode === 'sketch' ? '2D' : '3D'}
|
||||||
</span>
|
</span>
|
||||||
<menu className="flex flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
||||||
<ToolbarButtons />
|
<ToolbarButtons />
|
||||||
</menu>
|
</menu>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
@ -266,7 +335,7 @@ export const Toolbar = () => {
|
|||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<ToolbarButtons />
|
<ToolbarButtons className="flex-wrap" />
|
||||||
</section>
|
</section>
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
@ -23,10 +23,7 @@ type ActionButtonAsLink = BaseActionButtonProps &
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ActionButtonAsExternal = BaseActionButtonProps &
|
type ActionButtonAsExternal = BaseActionButtonProps &
|
||||||
Omit<
|
Omit<LinkProps, keyof BaseActionButtonProps> & {
|
||||||
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
||||||
keyof BaseActionButtonProps
|
|
||||||
> & {
|
|
||||||
Element: 'externalLink'
|
Element: 'externalLink'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,12 +66,17 @@ export const ActionButton = (props: ActionButtonProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
case 'externalLink': {
|
case 'externalLink': {
|
||||||
const { Element, icon, children, className, ...rest } = props
|
const { Element, to, icon, children, className, ...rest } = props
|
||||||
return (
|
return (
|
||||||
<a className={classNames} {...rest}>
|
<Link
|
||||||
|
to={to || paths.INDEX}
|
||||||
|
className={classNames}
|
||||||
|
{...rest}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
{icon && <ActionIcon {...icon} />}
|
{icon && <ActionIcon {...icon} />}
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { IconDefinition as BrandIconDefinition } from '@fortawesome/free-brands-svg-icons'
|
import { IconDefinition as BrandIconDefinition } from '@fortawesome/free-brands-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { CustomIcon, CustomIconName } from './CustomIcon'
|
||||||
|
|
||||||
const iconSizes = {
|
const iconSizes = {
|
||||||
sm: 12,
|
sm: 12,
|
||||||
@ -13,7 +14,7 @@ const iconSizes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionIconProps extends React.PropsWithChildren {
|
export interface ActionIconProps extends React.PropsWithChildren {
|
||||||
icon?: SolidIconDefinition | BrandIconDefinition
|
icon?: SolidIconDefinition | BrandIconDefinition | CustomIconName
|
||||||
className?: string
|
className?: string
|
||||||
bgClassName?: string
|
bgClassName?: string
|
||||||
iconClassName?: string
|
iconClassName?: string
|
||||||
@ -28,25 +29,39 @@ export const ActionIcon = ({
|
|||||||
size = 'md',
|
size = 'md',
|
||||||
children,
|
children,
|
||||||
}: ActionIconProps) => {
|
}: ActionIconProps) => {
|
||||||
|
// By default, we reverse the icon color and background color in dark mode
|
||||||
|
const computedIconClassName =
|
||||||
|
iconClassName ||
|
||||||
|
`text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50`
|
||||||
|
|
||||||
|
const computedBgClassName =
|
||||||
|
bgClassName ||
|
||||||
|
`bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10 group-disabled:bg-chalkboard-80 dark:group-disabled:bg-chalkboard-80`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
`p-${
|
`p-${
|
||||||
size === 'xl' ? '2' : '1'
|
size === 'xl' ? '2' : '1'
|
||||||
} w-fit inline-grid place-content-center ${className} ` +
|
} w-fit inline-grid place-content-center ${className} ` +
|
||||||
(bgClassName ||
|
computedBgClassName
|
||||||
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10')
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{children || (
|
{children ? (
|
||||||
|
children
|
||||||
|
) : typeof icon === 'string' ? (
|
||||||
|
<CustomIcon
|
||||||
|
name={icon}
|
||||||
|
width={iconSizes[size]}
|
||||||
|
height={iconSizes[size]}
|
||||||
|
className={computedIconClassName}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={icon}
|
icon={icon}
|
||||||
width={iconSizes[size]}
|
width={iconSizes[size]}
|
||||||
height={iconSizes[size]}
|
height={iconSizes[size]}
|
||||||
className={
|
className={computedIconClassName}
|
||||||
iconClassName ||
|
|
||||||
'text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-liquid-100 dark:group-hover:text-liquid-100 dark:hover:text-liquid-100'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,7 @@ export const AppHeader = ({
|
|||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={
|
className={
|
||||||
(showToolbar ? 'grid ' : 'flex justify-between ') +
|
(showToolbar ? 'w-full grid ' : 'flex justify-between ') +
|
||||||
styles.header +
|
styles.header +
|
||||||
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
|
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
|
||||||
className
|
className
|
||||||
@ -38,7 +38,7 @@ export const AppHeader = ({
|
|||||||
<ProjectSidebarMenu renderAsLink={!enableMenu} project={project} />
|
<ProjectSidebarMenu renderAsLink={!enableMenu} project={project} />
|
||||||
{/* Toolbar if the context deems it */}
|
{/* Toolbar if the context deems it */}
|
||||||
{showToolbar && (
|
{showToolbar && (
|
||||||
<div className="max-w-4xl">
|
<div className="max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
182
src/components/AstExplorer.tsx
Normal file
182
src/components/AstExplorer.tsx
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
|
||||||
|
export function AstExplorer() {
|
||||||
|
const { ast, setHighlightRange, selectionRanges } = useStore((s) => ({
|
||||||
|
ast: s.ast,
|
||||||
|
setHighlightRange: s.setHighlightRange,
|
||||||
|
selectionRanges: s.selectionRanges,
|
||||||
|
}))
|
||||||
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
selectionRanges.codeBasedSelections?.[0]?.range
|
||||||
|
)
|
||||||
|
const node = getNodeFromPath(ast, pathToNode).node
|
||||||
|
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative" style={{ width: '300px' }}>
|
||||||
|
<div className="">
|
||||||
|
filter out keys:<div className="w-2 inline-block"></div>
|
||||||
|
{['start', 'end', 'type'].map((key) => {
|
||||||
|
return (
|
||||||
|
<label key={key} className="inline-flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox"
|
||||||
|
checked={filterKeys.includes(key)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (filterKeys.includes(key)) {
|
||||||
|
setFilterKeys(filterKeys.filter((k) => k !== key))
|
||||||
|
} else {
|
||||||
|
setFilterKeys([...filterKeys, key])
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="mr-2">{key}</span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="h-full relative"
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
setHighlightRange([0, 0])
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}>
|
||||||
|
<DisplayObj obj={ast} filterKeys={filterKeys} node={node} />
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DisplayBody({
|
||||||
|
body,
|
||||||
|
filterKeys,
|
||||||
|
node,
|
||||||
|
}: {
|
||||||
|
body: { start: number; end: number; [key: string]: any }[]
|
||||||
|
filterKeys: string[]
|
||||||
|
node: any
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{body.map((b, index) => {
|
||||||
|
return (
|
||||||
|
<div className="my-2" key={index}>
|
||||||
|
<DisplayObj obj={b} filterKeys={filterKeys} node={node} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DisplayObj({
|
||||||
|
obj,
|
||||||
|
filterKeys,
|
||||||
|
node,
|
||||||
|
}: {
|
||||||
|
obj: { start: number; end: number; [key: string]: any }
|
||||||
|
filterKeys: string[]
|
||||||
|
node: any
|
||||||
|
}) {
|
||||||
|
const { setHighlightRange, setCursor2 } = useStore((s) => ({
|
||||||
|
setHighlightRange: s.setHighlightRange,
|
||||||
|
setCursor2: s.setCursor2,
|
||||||
|
}))
|
||||||
|
const ref = useRef<HTMLPreElement>(null)
|
||||||
|
const [hasCursor, setHasCursor] = useState(false)
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
node?.start === obj?.start &&
|
||||||
|
node?.end === obj?.end &&
|
||||||
|
node.type === obj?.type
|
||||||
|
) {
|
||||||
|
ref?.current?.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
|
||||||
|
setHasCursor(true)
|
||||||
|
} else {
|
||||||
|
setHasCursor(false)
|
||||||
|
}
|
||||||
|
}, [node.start, node.end, node.type])
|
||||||
|
return (
|
||||||
|
<pre
|
||||||
|
ref={ref}
|
||||||
|
className={`ml-2 border-l border-violet-600 pl-1 ${
|
||||||
|
hasCursor ? 'bg-violet-100/25' : ''
|
||||||
|
}`}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
setHighlightRange([obj?.start || 0, obj.end])
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
onMouseMove={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setHighlightRange([obj?.start || 0, obj.end])
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
setCursor2({ type: 'default', range: [obj?.start || 0, obj.end || 0] })
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isCollapsed ? (
|
||||||
|
<button
|
||||||
|
className="m-0 p-0 border-0"
|
||||||
|
onClick={() => setIsCollapsed(false)}
|
||||||
|
>
|
||||||
|
{'>'}type: {obj.type}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className="flex">
|
||||||
|
{/* <button className="m-0 p-0 border-0 mb-auto" onClick={() => setIsCollapsed(true)}>{'⬇️'}</button> */}
|
||||||
|
<ul className="inline-block">
|
||||||
|
{Object.entries(obj).map(([key, value]) => {
|
||||||
|
if (filterKeys.includes(key)) {
|
||||||
|
return null
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
{`${key}: [`}
|
||||||
|
<DisplayBody
|
||||||
|
body={value}
|
||||||
|
filterKeys={filterKeys}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
{']'}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
value?.end
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
{key}:
|
||||||
|
<DisplayObj
|
||||||
|
obj={value}
|
||||||
|
filterKeys={filterKeys}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
typeof value === 'string' ||
|
||||||
|
typeof value === 'number'
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
{key}: {value}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</pre>
|
||||||
|
)
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
import { useEffect, useState, useRef } from 'react'
|
import { useEffect, useState, useRef } from 'react'
|
||||||
import { parser_wasm } from '../lang/abstractSyntaxTree'
|
import { parse, BinaryPart, Value, executor } from '../lang/wasm'
|
||||||
import { BinaryPart, Value } from '../lang/abstractSyntaxTreeTypes'
|
|
||||||
import { executor } from '../lang/executor'
|
|
||||||
import {
|
import {
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
@ -10,6 +8,7 @@ import {
|
|||||||
} from '../lang/modifyAst'
|
} from '../lang/modifyAst'
|
||||||
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
export const AvailableVars = ({
|
export const AvailableVars = ({
|
||||||
onVarClick,
|
onVarClick,
|
||||||
@ -92,12 +91,12 @@ export function useCalc({
|
|||||||
newVariableInsertIndex: number
|
newVariableInsertIndex: number
|
||||||
setNewVariableName: (a: string) => void
|
setNewVariableName: (a: string) => void
|
||||||
} {
|
} {
|
||||||
const { ast, programMemory, selectionRange, engineCommandManager } = useStore(
|
const { ast, programMemory, selectionRange, defaultPlanes } = useStore(
|
||||||
(s) => ({
|
(s) => ({
|
||||||
ast: s.ast,
|
ast: s.ast,
|
||||||
programMemory: s.programMemory,
|
programMemory: s.programMemory,
|
||||||
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
|
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
|
||||||
engineCommandManager: s.engineCommandManager,
|
defaultPlanes: s.defaultPlanes,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
@ -140,27 +139,29 @@ export function useCalc({
|
|||||||
}, [ast, programMemory, selectionRange])
|
}, [ast, programMemory, selectionRange])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!engineCommandManager) return
|
|
||||||
try {
|
try {
|
||||||
const code = `const __result__ = ${value}\nshow(__result__)`
|
const code = `const __result__ = ${value}\nshow(__result__)`
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const _programMem: any = { root: {}, return: null }
|
const _programMem: any = { root: {}, return: null }
|
||||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||||
})
|
})
|
||||||
executor(ast, _programMem, engineCommandManager).then((programMemory) => {
|
if (!defaultPlanes) return
|
||||||
const resultDeclaration = ast.body.find(
|
executor(ast, _programMem, engineCommandManager, defaultPlanes!).then(
|
||||||
(a) =>
|
(programMemory) => {
|
||||||
a.type === 'VariableDeclaration' &&
|
const resultDeclaration = ast.body.find(
|
||||||
a.declarations?.[0]?.id?.name === '__result__'
|
(a) =>
|
||||||
)
|
a.type === 'VariableDeclaration' &&
|
||||||
const init =
|
a.declarations?.[0]?.id?.name === '__result__'
|
||||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
)
|
||||||
resultDeclaration?.declarations?.[0]?.init
|
const init =
|
||||||
const result = programMemory?.root?.__result__?.value
|
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
resultDeclaration?.declarations?.[0]?.init
|
||||||
init && setValueNode(init)
|
const result = programMemory?.root?.__result__?.value
|
||||||
})
|
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||||
|
init && setValueNode(init)
|
||||||
|
}
|
||||||
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setCalcResult('NAN')
|
setCalcResult('NAN')
|
||||||
setValueNode(null)
|
setValueNode(null)
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { Menu } from '@headlessui/react'
|
import { Menu } from '@headlessui/react'
|
||||||
import { PropsWithChildren } from 'react'
|
import { PropsWithChildren } from 'react'
|
||||||
import { faEllipsis } from '@fortawesome/free-solid-svg-icons'
|
import {
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faEllipsis,
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionIcon } from './ActionIcon'
|
import { ActionIcon } from './ActionIcon'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import styles from './CodeMenu.module.css'
|
import styles from './CodeMenu.module.css'
|
||||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||||
import { editorShortcutMeta } from './TextEditor'
|
import { editorShortcutMeta } from './TextEditor'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
|
||||||
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||||
const { formatCode } = useStore((s) => ({
|
const { formatCode } = useStore((s) => ({
|
||||||
@ -19,7 +23,8 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
|||||||
<div
|
<div
|
||||||
className="relative"
|
className="relative"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (e.eventPhase === 3) {
|
const target = e.target as HTMLElement
|
||||||
|
if (e.eventPhase === 3 && target.closest('a') === null) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
@ -52,6 +57,24 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
|||||||
</button>
|
</button>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
|
<Menu.Item>
|
||||||
|
<a
|
||||||
|
className={styles.button}
|
||||||
|
href="https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<span>Read the KCL docs</span>
|
||||||
|
<small>
|
||||||
|
On GitHub
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="ml-1 align-text-top"
|
||||||
|
width={12}
|
||||||
|
/>
|
||||||
|
</small>
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
</Menu.Items>
|
</Menu.Items>
|
||||||
</div>
|
</div>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
@ -62,7 +62,7 @@ export const CommandBarProvider = ({
|
|||||||
|
|
||||||
const CommandBar = () => {
|
const CommandBar = () => {
|
||||||
const { commands, commandBarOpen, setCommandBarOpen } = useCommandsContext()
|
const { commands, commandBarOpen, setCommandBarOpen } = useCommandsContext()
|
||||||
useHotkeys('meta+k', () => {
|
useHotkeys(['meta+k', 'meta+/'], () => {
|
||||||
if (commands.length === 0) return
|
if (commands.length === 0) return
|
||||||
setCommandBarOpen(!commandBarOpen)
|
setCommandBarOpen(!commandBarOpen)
|
||||||
})
|
})
|
||||||
@ -221,10 +221,10 @@ const CommandBar = () => {
|
|||||||
<Combobox
|
<Combobox
|
||||||
value={selectedCommand}
|
value={selectedCommand}
|
||||||
onChange={handleCommandSelection}
|
onChange={handleCommandSelection}
|
||||||
className="rounded relative mx-auto p-2 bg-chalkboard-10 dark:bg-chalkboard-100 border dark:border-chalkboard-70 max-w-xl w-full shadow-lg"
|
className="relative w-full max-w-xl p-2 mx-auto border rounded shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70"
|
||||||
as="div"
|
as="div"
|
||||||
>
|
>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex items-center gap-2">
|
||||||
<ActionIcon icon={faSearch} size="xl" className="rounded-sm" />
|
<ActionIcon icon={faSearch} size="xl" className="rounded-sm" />
|
||||||
<div>
|
<div>
|
||||||
{inSubCommand && (
|
{inSubCommand && (
|
||||||
@ -235,7 +235,7 @@ const CommandBar = () => {
|
|||||||
)}
|
)}
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
onChange={(event) => setQuery(event.target.value)}
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
className="bg-transparent focus:outline-none w-full"
|
className="w-full bg-transparent focus:outline-none"
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.metaKey && event.key === 'k')
|
if (event.metaKey && event.key === 'k')
|
||||||
setCommandBarOpen(false)
|
setCommandBarOpen(false)
|
||||||
@ -264,12 +264,12 @@ const CommandBar = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Combobox.Options static className="max-h-96 overflow-y-auto">
|
<Combobox.Options static className="overflow-y-auto max-h-96">
|
||||||
{filteredCommands?.map((commandResult) => (
|
{filteredCommands?.map((commandResult) => (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
key={commandResult.item.name}
|
key={commandResult.item.name}
|
||||||
value={commandResult}
|
value={commandResult}
|
||||||
className="my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90 py-1 px-2"
|
className="px-2 py-1 my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90"
|
||||||
>
|
>
|
||||||
<p>{commandResult.item.name}</p>
|
<p>{commandResult.item.name}</p>
|
||||||
{(commandResult.item as SubCommand).description && (
|
{(commandResult.item as SubCommand).description && (
|
||||||
|
161
src/components/CustomIcon.tsx
Normal file
161
src/components/CustomIcon.tsx
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
export type CustomIconName =
|
||||||
|
| 'equal'
|
||||||
|
| 'exit'
|
||||||
|
| 'extrude'
|
||||||
|
| 'horizontal'
|
||||||
|
| 'line'
|
||||||
|
| 'move'
|
||||||
|
| 'parallel'
|
||||||
|
| 'sketch'
|
||||||
|
| 'vertical'
|
||||||
|
|
||||||
|
export const CustomIcon = ({
|
||||||
|
name,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
name: CustomIconName
|
||||||
|
} & React.SVGProps<SVGSVGElement>) => {
|
||||||
|
switch (name) {
|
||||||
|
case 'equal':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5 8.78V7H14.52V8.78H5ZM5 13.02V11.24H14.52V13.02H5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'exit':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M17 10L3 10M3 10L6.5 6.5M3 10L6.5 13.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
case 'extrude':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10 3L10.3536 3.35355L12.3536 5.35355L11.6465 6.06066L10.5 4.91421V11.5854C11.0826 11.7913 11.5 12.3469 11.5 13C11.5 13.8284 10.8284 14.5 10 14.5C9.17157 14.5 8.5 13.8284 8.5 13C8.5 12.3469 8.91741 11.7913 9.5 11.5854V4.91421L8.35356 6.06066L7.64645 5.35355L9.64645 3.35355L10 3ZM1.95887 12.3282L8 8.63644V9.80838L2.91773 12.9142L10 17.2423L17.0823 12.9142L12 9.80838V8.63644L18.0411 12.3282L19 12.9142L19 14.9683H18V13.5253L10.5 18.1087V19.9683H9.5V18.1087L2 13.5253V14.9683H1L1 12.9142L1.95887 12.3282Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'horizontal':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M4 9.5H16V11.5H4V9.5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'line':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M15.5 6C16.3284 6 17 5.32843 17 4.5C17 3.67157 16.3284 3 15.5 3C14.6716 3 14 3.67157 14 4.5C14 4.73107 14.0522 4.94993 14.1456 5.14543L5.14543 14.1456C4.94993 14.0522 4.73107 14 4.5 14C3.67157 14 3 14.6716 3 15.5C3 16.3284 3.67157 17 4.5 17C5.32843 17 6 16.3284 6 15.5C6 15.2679 5.94729 15.0482 5.8532 14.852L14.852 5.8532C15.0482 5.94729 15.2679 6 15.5 6Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'move':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10 2.29289L10.3536 2.64645L12.3536 4.64645L11.6465 5.35355L10.5 4.20711V8V9.50001H12L15.7929 9.50001L14.6465 8.35356L15.3536 7.64645L17.3536 9.64645L17.7071 10L17.3536 10.3536L15.3536 12.3536L14.6465 11.6465L15.7929 10.5H12H10.5V12V15.7929L11.6465 14.6464L12.3536 15.3536L10.3536 17.3536L10 17.7071L9.64645 17.3536L7.64645 15.3536L8.35356 14.6464L9.50001 15.7929V12V10.5H8.00001H4.20712L5.35357 11.6465L4.64646 12.3536L2.64646 10.3536L2.29291 10L2.64646 9.64645L4.64646 7.64645L5.35357 8.35356L4.20712 9.50001H8.00001H9.50001V8V4.20711L8.35356 5.35355L7.64645 4.64645L9.64645 2.64645L10 2.29289Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'parallel':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M8 16V4H6V16H8ZM14 16V4H12V16H14Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'sketch':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M14.8037 13.4035L15.5509 14.1635L16.3682 16.8386L13.5521 16.1346L12.8186 15.3885L14.8037 13.4035ZM14.1025 12.6903L12.1175 14.6754L3.48609 5.89624C2.94588 5.34678 2.94963 4.46456 3.49448 3.91971C4.04591 3.36828 4.94112 3.37208 5.48786 3.92817L14.1025 12.6903ZM6.20094 3.22709L16.4357 13.6371L17.5003 17.1216L17.8412 18.2376L16.7091 17.9546L13.0364 17.0364L2.77301 6.59732C1.84793 5.6564 1.85434 4.14564 2.78737 3.2126C3.73167 2.2683 5.26468 2.27481 6.20094 3.22709Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
case 'vertical':
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M11 4V16H9V4H11Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,12 @@
|
|||||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||||
import { useStore } from '../useStore'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { EngineCommand } from '../lang/std/engineConnection'
|
import { EngineCommand } from '../lang/std/engineConnection'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { ActionButton } from '../components/ActionButton'
|
import { ActionButton } from '../components/ActionButton'
|
||||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { isReducedMotion } from 'lang/util'
|
import { isReducedMotion } from 'lang/util'
|
||||||
|
import { AstExplorer } from './AstExplorer'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
type SketchModeCmd = Extract<
|
type SketchModeCmd = Extract<
|
||||||
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
|
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
|
||||||
@ -13,9 +14,6 @@ type SketchModeCmd = Extract<
|
|||||||
>
|
>
|
||||||
|
|
||||||
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
||||||
const { engineCommandManager } = useStore((s) => ({
|
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
}))
|
|
||||||
const [sketchModeCmd, setSketchModeCmd] = useState<SketchModeCmd>({
|
const [sketchModeCmd, setSketchModeCmd] = useState<SketchModeCmd>({
|
||||||
type: 'default_camera_enable_sketch_mode',
|
type: 'default_camera_enable_sketch_mode',
|
||||||
origin: { x: 0, y: 0, z: 0 },
|
origin: { x: 0, y: 0, z: 0 },
|
||||||
@ -29,7 +27,11 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
|||||||
return (
|
return (
|
||||||
<CollapsiblePanel
|
<CollapsiblePanel
|
||||||
{...props}
|
{...props}
|
||||||
className={'!absolute !h-auto bottom-5 right-5 ' + className}
|
className={
|
||||||
|
'!absolute overflow-hidden !h-auto bottom-5 right-5 ' + className
|
||||||
|
}
|
||||||
|
// header height, top-5, and bottom-5
|
||||||
|
style={{ maxHeight: 'calc(100% - 3rem - 1.25rem - 1.25rem)' }}
|
||||||
>
|
>
|
||||||
<section className="p-4 flex flex-col gap-4">
|
<section className="p-4 flex flex-col gap-4">
|
||||||
<Xyz
|
<Xyz
|
||||||
@ -65,19 +67,18 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
|||||||
className="w-16"
|
className="w-16"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={sketchModeCmd.ortho}
|
checked={sketchModeCmd.ortho}
|
||||||
onChange={(a) => {
|
onChange={(a) =>
|
||||||
console.log(a, (a as any).checked)
|
|
||||||
setSketchModeCmd({
|
setSketchModeCmd({
|
||||||
...sketchModeCmd,
|
...sketchModeCmd,
|
||||||
ortho: a.target.checked,
|
ortho: a.target.checked,
|
||||||
})
|
})
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: sketchModeCmd,
|
cmd: sketchModeCmd,
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
@ -94,6 +95,9 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
|||||||
>
|
>
|
||||||
Send sketch mode command
|
Send sketch mode command
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
<div style={{ height: '400px' }} className="overflow-y-auto">
|
||||||
|
<AstExplorer />
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</CollapsiblePanel>
|
</CollapsiblePanel>
|
||||||
)
|
)
|
||||||
|
@ -40,12 +40,12 @@ const DownloadAppBanner = () => {
|
|||||||
</code>
|
</code>
|
||||||
, and isn't backed up anywhere! Visit{' '}
|
, and isn't backed up anywhere! Visit{' '}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/KittyCAD/modeling-app/releases"
|
href="https://kittycad.io/modeling-app/download"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
|
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
|
||||||
>
|
>
|
||||||
our GitHub repository
|
our website
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
to download the app for the best experience.
|
to download the app for the best experience.
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,8 +1,60 @@
|
|||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import { useRouteError, isRouteErrorResponse } from 'react-router-dom'
|
||||||
|
import { ActionButton } from './ActionButton'
|
||||||
|
import {
|
||||||
|
faBug,
|
||||||
|
faHome,
|
||||||
|
faRefresh,
|
||||||
|
faTrash,
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
export const ErrorPage = () => {
|
export const ErrorPage = () => {
|
||||||
|
let error = useRouteError()
|
||||||
|
|
||||||
|
console.error('error', error)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-screen">
|
<div className="flex flex-col items-center justify-center h-screen">
|
||||||
<h1 className="text-4xl font-bold">404</h1>
|
<section className="max-w-full xl:max-w-4xl mx-auto">
|
||||||
<p className="text-2xl font-bold">Page not found</p>
|
<h1 className="text-4xl mb-8 font-bold">
|
||||||
|
An unexpected error occurred
|
||||||
|
</h1>
|
||||||
|
{isRouteErrorResponse(error) && (
|
||||||
|
<p className="mb-8">
|
||||||
|
{error.status}: {error.data}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-between gap-2 mt-6">
|
||||||
|
{isTauri() && (
|
||||||
|
<ActionButton Element="link" to={'/'} icon={{ icon: faHome }}>
|
||||||
|
Go Home
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
icon={{ icon: faRefresh }}
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
Reload
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
icon={{ icon: faTrash }}
|
||||||
|
onClick={() => {
|
||||||
|
window.localStorage.clear()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear storage
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="externalLink"
|
||||||
|
icon={{ icon: faBug }}
|
||||||
|
to="https://github.com/KittyCAD/modeling-app/issues/new"
|
||||||
|
>
|
||||||
|
Report Bug
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useStore } from '../useStore'
|
|
||||||
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
|
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import Modal from 'react-modal'
|
import Modal from 'react-modal'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
|
||||||
type OutputFormat = Models['OutputFormat_type']
|
type OutputFormat = Models['OutputFormat_type']
|
||||||
|
type OutputTypeKey = OutputFormat['type']
|
||||||
|
type ExtractStorageTypes<T> = T extends { storage: infer U } ? U : never
|
||||||
|
type StorageUnion = ExtractStorageTypes<OutputFormat>
|
||||||
|
|
||||||
interface ExportButtonProps extends React.PropsWithChildren {
|
interface ExportButtonProps extends React.PropsWithChildren {
|
||||||
className?: {
|
className?: {
|
||||||
@ -18,14 +22,19 @@ interface ExportButtonProps extends React.PropsWithChildren {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
||||||
const { engineCommandManager } = useStore((s) => ({
|
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const [modalIsOpen, setIsOpen] = React.useState(false)
|
const [modalIsOpen, setIsOpen] = React.useState(false)
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
state: {
|
||||||
|
context: { baseUnit },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
const defaultType = 'gltf'
|
const defaultType = 'gltf'
|
||||||
const [type, setType] = React.useState(defaultType)
|
const [type, setType] = React.useState<OutputTypeKey>(defaultType)
|
||||||
|
const defaultStorage = 'embedded'
|
||||||
|
const [storage, setStorage] = React.useState<StorageUnion>(defaultStorage)
|
||||||
|
|
||||||
function openModal() {
|
function openModal() {
|
||||||
setIsOpen(true)
|
setIsOpen(true)
|
||||||
@ -38,7 +47,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
|||||||
// Default to gltf and embedded.
|
// Default to gltf and embedded.
|
||||||
const initialValues: OutputFormat = {
|
const initialValues: OutputFormat = {
|
||||||
type: defaultType,
|
type: defaultType,
|
||||||
storage: 'embedded',
|
storage: defaultStorage,
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
}
|
}
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
@ -66,7 +75,18 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
engineCommandManager?.sendSceneCommand({
|
if (values.type === 'obj' || values.type === 'stl') {
|
||||||
|
values.units = baseUnit
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
values.type === 'ply' ||
|
||||||
|
values.type === 'stl' ||
|
||||||
|
values.type === 'gltf'
|
||||||
|
) {
|
||||||
|
// Set the storage type.
|
||||||
|
values.storage = storage
|
||||||
|
}
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'export',
|
type: 'export',
|
||||||
@ -75,6 +95,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
|||||||
// in the scene to export. In that case, you'd pass the IDs thru here.
|
// in the scene to export. In that case, you'd pass the IDs thru here.
|
||||||
entity_ids: [],
|
entity_ids: [],
|
||||||
format: values,
|
format: values,
|
||||||
|
source_unit: baseUnit,
|
||||||
},
|
},
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
})
|
})
|
||||||
@ -109,7 +130,17 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
|||||||
id="type"
|
id="type"
|
||||||
name="type"
|
name="type"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setType(e.target.value)
|
setType(e.target.value as OutputTypeKey)
|
||||||
|
if (e.target.value === 'gltf') {
|
||||||
|
// Set default to embedded.
|
||||||
|
setStorage('embedded')
|
||||||
|
} else if (e.target.value === 'ply') {
|
||||||
|
// Set default to ascii.
|
||||||
|
setStorage('ascii')
|
||||||
|
} else if (e.target.value === 'stl') {
|
||||||
|
// Set default to ascii.
|
||||||
|
setStorage('ascii')
|
||||||
|
}
|
||||||
formik.handleChange(e)
|
formik.handleChange(e)
|
||||||
}}
|
}}
|
||||||
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
|
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
|
||||||
@ -127,10 +158,10 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
|||||||
<select
|
<select
|
||||||
id="storage"
|
id="storage"
|
||||||
name="storage"
|
name="storage"
|
||||||
onChange={formik.handleChange}
|
onChange={(e) => {
|
||||||
value={
|
setStorage(e.target.value as StorageUnion)
|
||||||
'storage' in formik.values ? formik.values.storage : ''
|
formik.handleChange(e)
|
||||||
}
|
}}
|
||||||
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
|
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
|
||||||
>
|
>
|
||||||
{type === 'gltf' && (
|
{type === 'gltf' && (
|
||||||
|
@ -24,6 +24,9 @@ import {
|
|||||||
StateFrom,
|
StateFrom,
|
||||||
} from 'xstate'
|
} from 'xstate'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
|
import { invoke } from '@tauri-apps/api'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import { VITE_KC_API_BASE_URL } from 'env'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -108,6 +111,7 @@ export const GlobalStateProvider = ({
|
|||||||
actions: {
|
actions: {
|
||||||
goToSignInPage: () => {
|
goToSignInPage: () => {
|
||||||
navigate(paths.SIGN_IN)
|
navigate(paths.SIGN_IN)
|
||||||
|
|
||||||
logout()
|
logout()
|
||||||
},
|
},
|
||||||
goToIndexPage: () => {
|
goToIndexPage: () => {
|
||||||
@ -149,10 +153,12 @@ export const GlobalStateProvider = ({
|
|||||||
export default GlobalStateProvider
|
export default GlobalStateProvider
|
||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
const url = withBaseUrl('/logout')
|
|
||||||
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
||||||
return fetch(url, {
|
return (
|
||||||
method: 'POST',
|
!isTauri() &&
|
||||||
credentials: 'include',
|
fetch(withBaseUrl('/logout'), {
|
||||||
})
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { processMemory } from './MemoryPanel'
|
import { processMemory } from './MemoryPanel'
|
||||||
import { parser_wasm } from '../lang/abstractSyntaxTree'
|
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { initPromise } from '../lang/rust'
|
import { initPromise, parse } from '../lang/wasm'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
@ -10,23 +9,25 @@ describe('processMemory', () => {
|
|||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const code = `
|
const code = `
|
||||||
const myVar = 5
|
const myVar = 5
|
||||||
const myFn = (a) => {
|
fn myFn = (a) => {
|
||||||
return a - 2
|
return a - 2
|
||||||
}
|
}
|
||||||
const otherVar = myFn(5)
|
const otherVar = myFn(5)
|
||||||
|
|
||||||
const theExtrude = startSketchAt([0, 0])
|
const theExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-2.4, myVar], %)
|
|> lineTo([-2.4, myVar], %)
|
||||||
|> lineTo([-0.76, otherVar], %)
|
|> lineTo([-0.76, otherVar], %)
|
||||||
|> extrude(4, %)
|
|> extrude(4, %)
|
||||||
|
|
||||||
const theSketch = startSketchAt([0, 0])
|
const theSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-3.35, 0.17], %)
|
|> lineTo([-3.35, 0.17], %)
|
||||||
|> lineTo([0.98, 5.16], %)
|
|> lineTo([0.98, 5.16], %)
|
||||||
|> lineTo([2.15, 4.32], %)
|
|> lineTo([2.15, 4.32], %)
|
||||||
// |> rx(90, %)
|
// |> rx(90, %)
|
||||||
show(theExtrude, theSketch)`
|
show(theExtrude, theSketch)`
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast, {
|
const programMemory = await enginelessExecutor(ast, {
|
||||||
root: {},
|
root: {},
|
||||||
return: null,
|
return: null,
|
||||||
|
@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
|
|||||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/executor'
|
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
|
||||||
import { Themes } from '../lib/theme'
|
import { Themes } from '../lib/theme'
|
||||||
|
|
||||||
interface MemoryPanelProps extends CollapsiblePanelProps {
|
interface MemoryPanelProps extends CollapsiblePanelProps {
|
||||||
@ -24,7 +24,11 @@ export const MemoryPanel = ({
|
|||||||
<CollapsiblePanel {...props}>
|
<CollapsiblePanel {...props}>
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<div className="absolute inset-0 flex flex-col items-start">
|
<div className="absolute inset-0 flex flex-col items-start">
|
||||||
<div className=" h-full console-tile w-full">
|
<div
|
||||||
|
className="overflow-y-auto h-full console-tile w-full"
|
||||||
|
style={{ marginBottom: 36 }}
|
||||||
|
>
|
||||||
|
{/* 36px is the height of PanelHeader */}
|
||||||
<ReactJson
|
<ReactJson
|
||||||
src={ProcessedMemory}
|
src={ProcessedMemory}
|
||||||
collapsed={1}
|
collapsed={1}
|
||||||
@ -46,7 +50,7 @@ export const MemoryPanel = ({
|
|||||||
|
|
||||||
export const processMemory = (programMemory: ProgramMemory) => {
|
export const processMemory = (programMemory: ProgramMemory) => {
|
||||||
const processedMemory: any = {}
|
const processedMemory: any = {}
|
||||||
Object.keys(programMemory.root).forEach((key) => {
|
Object.keys(programMemory?.root || {}).forEach((key) => {
|
||||||
const val = programMemory.root[key]
|
const val = programMemory.root[key]
|
||||||
if (typeof val.value !== 'function') {
|
if (typeof val.value !== 'function') {
|
||||||
if (val.type === 'SketchGroup') {
|
if (val.type === 'SketchGroup') {
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
import { invoke } from '@tauri-apps/api/tauri'
|
|
||||||
import { open } from '@tauri-apps/api/dialog'
|
|
||||||
import { useStore } from '../useStore'
|
|
||||||
|
|
||||||
export const OpenFileButton = () => {
|
|
||||||
const { setCode } = useStore((s) => ({
|
|
||||||
setCode: s.setCode,
|
|
||||||
}))
|
|
||||||
const handleClick = async () => {
|
|
||||||
const selected = await open({
|
|
||||||
multiple: false,
|
|
||||||
directory: false,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: 'CAD',
|
|
||||||
extensions: ['toml'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
if (Array.isArray(selected)) {
|
|
||||||
// User selected multiple files
|
|
||||||
// We should not get here, since multiple is false.
|
|
||||||
} else if (selected === null) {
|
|
||||||
// User cancelled the selection
|
|
||||||
// Do nothing.
|
|
||||||
} else {
|
|
||||||
// User selected a single file
|
|
||||||
// We want to invoke our command to read the file.
|
|
||||||
const json: string = await invoke('read_toml', { path: selected })
|
|
||||||
const packageDetails = JSON.parse(json).package
|
|
||||||
if (packageDetails.main) {
|
|
||||||
const absPath = [
|
|
||||||
...selected.split('/').slice(0, -1),
|
|
||||||
packageDetails.main,
|
|
||||||
].join('/')
|
|
||||||
const file: string = await invoke('read_txt_file', { path: absPath })
|
|
||||||
setCode(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <button onClick={() => handleClick()}>Open File</button>
|
|
||||||
}
|
|
@ -2,6 +2,8 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
|||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { ProjectWithEntryPointMetadata } from '../Router'
|
import { ProjectWithEntryPointMetadata } from '../Router'
|
||||||
|
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||||
|
import CommandBarProvider from './CommandBar'
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const projectWellFormed = {
|
const projectWellFormed = {
|
||||||
@ -38,7 +40,11 @@ describe('ProjectSidebarMenu tests', () => {
|
|||||||
test('Renders the project name', () => {
|
test('Renders the project name', () => {
|
||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ProjectSidebarMenu project={projectWellFormed} />
|
<CommandBarProvider>
|
||||||
|
<GlobalStateProvider>
|
||||||
|
<ProjectSidebarMenu project={projectWellFormed} />
|
||||||
|
</GlobalStateProvider>
|
||||||
|
</CommandBarProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,7 +61,11 @@ describe('ProjectSidebarMenu tests', () => {
|
|||||||
test('Renders app name if given no project', () => {
|
test('Renders app name if given no project', () => {
|
||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ProjectSidebarMenu />
|
<CommandBarProvider>
|
||||||
|
<GlobalStateProvider>
|
||||||
|
<ProjectSidebarMenu />
|
||||||
|
</GlobalStateProvider>
|
||||||
|
</CommandBarProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -69,7 +79,14 @@ describe('ProjectSidebarMenu tests', () => {
|
|||||||
test('Renders as a link if set to do so', () => {
|
test('Renders as a link if set to do so', () => {
|
||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ProjectSidebarMenu project={projectWellFormed} renderAsLink={true} />
|
<CommandBarProvider>
|
||||||
|
<GlobalStateProvider>
|
||||||
|
<ProjectSidebarMenu
|
||||||
|
project={projectWellFormed}
|
||||||
|
renderAsLink={true}
|
||||||
|
/>
|
||||||
|
</GlobalStateProvider>
|
||||||
|
</CommandBarProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ const ProjectSidebarMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return renderAsLink ? (
|
return renderAsLink ? (
|
||||||
<Link
|
<Link
|
||||||
to={'../'}
|
to={paths.HOME}
|
||||||
className="flex items-center gap-4 my-2"
|
className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
|
||||||
data-testid="project-sidebar-link"
|
data-testid="project-sidebar-link"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@ -26,7 +26,7 @@ const ProjectSidebarMenu = ({
|
|||||||
className="h-9 w-auto"
|
className="h-9 w-auto"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max"
|
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block"
|
||||||
data-testid="project-sidebar-link-name"
|
data-testid="project-sidebar-link-name"
|
||||||
>
|
>
|
||||||
{project?.name ? project.name : 'KittyCAD Modeling App'}
|
{project?.name ? project.name : 'KittyCAD Modeling App'}
|
||||||
@ -35,15 +35,15 @@ const ProjectSidebarMenu = ({
|
|||||||
) : (
|
) : (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className="border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
|
className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
|
||||||
data-testid="project-sidebar-toggle"
|
data-testid="project-sidebar-toggle"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/kitt-8bit-winking.svg"
|
src="/kitt-8bit-winking.svg"
|
||||||
alt="KittyCAD App"
|
alt="KittyCAD App"
|
||||||
className="h-9 w-auto"
|
className="h-full w-auto"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max">
|
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block">
|
||||||
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
|
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
|
||||||
</span>
|
</span>
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Dialog, Transition } from '@headlessui/react'
|
import { Dialog, Transition } from '@headlessui/react'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { Value } from '../lang/abstractSyntaxTreeTypes'
|
import { Value } from '../lang/wasm'
|
||||||
import {
|
import {
|
||||||
AvailableVars,
|
AvailableVars,
|
||||||
addToInputHelper,
|
addToInputHelper,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Dialog, Transition } from '@headlessui/react'
|
import { Dialog, Transition } from '@headlessui/react'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { Value } from '../lang/abstractSyntaxTreeTypes'
|
import { Value } from '../lang/wasm'
|
||||||
import {
|
import {
|
||||||
AvailableVars,
|
AvailableVars,
|
||||||
addToInputHelper,
|
addToInputHelper,
|
||||||
|
@ -14,7 +14,21 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
|||||||
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { addStartSketch } from 'lang/modifyAst'
|
import { addStartSketch } from 'lang/modifyAst'
|
||||||
import { addNewSketchLn } from 'lang/std/sketch'
|
import {
|
||||||
|
addCloseToPipe,
|
||||||
|
addNewSketchLn,
|
||||||
|
compareVec2Epsilon,
|
||||||
|
} from 'lang/std/sketch'
|
||||||
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
|
import {
|
||||||
|
Program,
|
||||||
|
VariableDeclarator,
|
||||||
|
rangeTypeFix,
|
||||||
|
modifyAstForSketch,
|
||||||
|
} from 'lang/wasm'
|
||||||
|
import { KCLError } from 'lang/errors'
|
||||||
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
export const Stream = ({ className = '' }) => {
|
export const Stream = ({ className = '' }) => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@ -22,7 +36,6 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
const {
|
const {
|
||||||
mediaStream,
|
mediaStream,
|
||||||
engineCommandManager,
|
|
||||||
setButtonDownInStream,
|
setButtonDownInStream,
|
||||||
didDragInStream,
|
didDragInStream,
|
||||||
setDidDragInStream,
|
setDidDragInStream,
|
||||||
@ -33,9 +46,10 @@ export const Stream = ({ className = '' }) => {
|
|||||||
updateAst,
|
updateAst,
|
||||||
setGuiMode,
|
setGuiMode,
|
||||||
programMemory,
|
programMemory,
|
||||||
|
defaultPlanes,
|
||||||
|
currentPlane,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
mediaStream: s.mediaStream,
|
mediaStream: s.mediaStream,
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
setButtonDownInStream: s.setButtonDownInStream,
|
setButtonDownInStream: s.setButtonDownInStream,
|
||||||
fileId: s.fileId,
|
fileId: s.fileId,
|
||||||
didDragInStream: s.didDragInStream,
|
didDragInStream: s.didDragInStream,
|
||||||
@ -47,6 +61,8 @@ export const Stream = ({ className = '' }) => {
|
|||||||
updateAst: s.updateAst,
|
updateAst: s.updateAst,
|
||||||
setGuiMode: s.setGuiMode,
|
setGuiMode: s.setGuiMode,
|
||||||
programMemory: s.programMemory,
|
programMemory: s.programMemory,
|
||||||
|
defaultPlanes: s.defaultPlanes,
|
||||||
|
currentPlane: s.currentPlane,
|
||||||
}))
|
}))
|
||||||
const {
|
const {
|
||||||
settings: {
|
settings: {
|
||||||
@ -63,7 +79,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
if (!mediaStream) return
|
if (!mediaStream) return
|
||||||
videoRef.current.srcObject = mediaStream
|
videoRef.current.srcObject = mediaStream
|
||||||
}, [mediaStream, engineCommandManager])
|
}, [mediaStream])
|
||||||
|
|
||||||
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
|
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
@ -97,7 +113,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'handle_mouse_drag_start',
|
type: 'handle_mouse_drag_start',
|
||||||
@ -111,7 +127,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
guiMode.sketchMode === ('sketch_line' as any)
|
guiMode.sketchMode === ('sketch_line' as any)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'camera_drag_start',
|
type: 'camera_drag_start',
|
||||||
@ -129,7 +145,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
|
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
|
||||||
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
||||||
|
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_zoom',
|
type: 'default_camera_zoom',
|
||||||
@ -167,7 +183,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!didDragInStream) {
|
if (!didDragInStream) {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'select_with_point',
|
type: 'select_with_point',
|
||||||
@ -204,29 +220,128 @@ export const Stream = ({ className = '' }) => {
|
|||||||
window: { x, y },
|
window: { x, y },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
engineCommandManager?.sendSceneCommand(command).then(async ({ data }) => {
|
engineCommandManager.sendSceneCommand(command).then(async (resp) => {
|
||||||
if (command.cmd.type !== 'mouse_click' || !ast) return
|
if (!(guiMode.mode === 'sketch')) return
|
||||||
if (
|
|
||||||
!(
|
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === ('sketch_line' as any as 'line')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (data?.data?.entities_modified?.length && guiMode.waitingFirstClick) {
|
if (guiMode.sketchMode === 'selectFace') return
|
||||||
const curve = await engineCommandManager?.sendSceneCommand({
|
|
||||||
|
// Check if the sketch group already exists.
|
||||||
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
guiMode.pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
).node
|
||||||
|
const variableName = varDec?.id?.name
|
||||||
|
const sketchGroup = programMemory.root[variableName]
|
||||||
|
const isEditingExistingSketch =
|
||||||
|
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
|
||||||
|
let sketchGroupId = ''
|
||||||
|
if (sketchGroup && sketchGroup.type === 'SketchGroup') {
|
||||||
|
sketchGroupId = sketchGroup.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
guiMode.sketchMode === ('move' as any as 'line') &&
|
||||||
|
command.cmd.type === 'handle_mouse_drag_end'
|
||||||
|
) {
|
||||||
|
// Let's get the updated ast.
|
||||||
|
if (sketchGroupId === '') return
|
||||||
|
// We have a problem if we do not have an id for the sketch group.
|
||||||
|
if (
|
||||||
|
guiMode.pathId === undefined ||
|
||||||
|
guiMode.pathId === null ||
|
||||||
|
guiMode.pathId === ''
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
let engineId = guiMode.pathId
|
||||||
|
|
||||||
|
// Get the current plane string for plane we are on.
|
||||||
|
let currentPlaneString = ''
|
||||||
|
if (currentPlane === defaultPlanes?.xy) {
|
||||||
|
currentPlaneString = 'XY'
|
||||||
|
} else if (currentPlane === defaultPlanes?.yz) {
|
||||||
|
currentPlaneString = 'YZ'
|
||||||
|
} else if (currentPlane === defaultPlanes?.xz) {
|
||||||
|
currentPlaneString = 'XZ'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not supporting editing/moving lines on a non-default plane.
|
||||||
|
// Eventually we can support this but for now we will just throw an
|
||||||
|
// error.
|
||||||
|
if (currentPlaneString === '') return
|
||||||
|
|
||||||
|
const updatedAst: Program = await modifyAstForSketch(
|
||||||
|
engineCommandManager,
|
||||||
|
ast,
|
||||||
|
variableName,
|
||||||
|
currentPlaneString,
|
||||||
|
engineId
|
||||||
|
)
|
||||||
|
|
||||||
|
updateAst(updatedAst, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command?.cmd?.type !== 'mouse_click' || !ast) return
|
||||||
|
|
||||||
|
if (!(guiMode.sketchMode === ('sketch_line' as any as 'line'))) return
|
||||||
|
|
||||||
|
if (
|
||||||
|
resp?.data?.data?.entities_modified?.length &&
|
||||||
|
guiMode.waitingFirstClick &&
|
||||||
|
!isEditingExistingSketch
|
||||||
|
) {
|
||||||
|
const curve = await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'curve_get_control_points',
|
type: 'curve_get_control_points',
|
||||||
curve_id: data?.data?.entities_modified[0],
|
curve_id: resp?.data?.data?.entities_modified[0],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const coords: { x: number; y: number }[] =
|
const coords: { x: number; y: number }[] =
|
||||||
curve.data.data.control_points
|
curve.data.data.control_points
|
||||||
|
|
||||||
|
// We need the normal for the plane we are on.
|
||||||
|
const plane = await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'get_sketch_mode_plane',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const z_axis = plane.data.data.z_axis
|
||||||
|
|
||||||
|
// Get the current axis.
|
||||||
|
let currentAxis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null =
|
||||||
|
null
|
||||||
|
if (currentPlane === defaultPlanes?.xy) {
|
||||||
|
if (z_axis.z === -1) {
|
||||||
|
currentAxis = '-xy'
|
||||||
|
} else {
|
||||||
|
currentAxis = 'xy'
|
||||||
|
}
|
||||||
|
} else if (currentPlane === defaultPlanes?.yz) {
|
||||||
|
if (z_axis.x === -1) {
|
||||||
|
currentAxis = '-yz'
|
||||||
|
} else {
|
||||||
|
currentAxis = 'yz'
|
||||||
|
}
|
||||||
|
} else if (currentPlane === defaultPlanes?.xz) {
|
||||||
|
if (z_axis.y === -1) {
|
||||||
|
currentAxis = '-xz'
|
||||||
|
} else {
|
||||||
|
currentAxis = 'xz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not support starting a new sketch on a non-default plane.
|
||||||
|
if (!currentAxis) return
|
||||||
|
|
||||||
const _addStartSketch = addStartSketch(
|
const _addStartSketch = addStartSketch(
|
||||||
ast,
|
ast,
|
||||||
|
currentAxis,
|
||||||
[roundOff(coords[0].x), roundOff(coords[0].y)],
|
[roundOff(coords[0].x), roundOff(coords[0].y)],
|
||||||
[
|
[
|
||||||
roundOff(coords[1].x - coords[0].x),
|
roundOff(coords[1].x - coords[0].x),
|
||||||
@ -236,34 +351,83 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const _modifiedAst = _addStartSketch.modifiedAst
|
const _modifiedAst = _addStartSketch.modifiedAst
|
||||||
const _pathToNode = _addStartSketch.pathToNode
|
const _pathToNode = _addStartSketch.pathToNode
|
||||||
|
|
||||||
|
// We need to update the guiMode with the right pathId so that we can
|
||||||
|
// move lines later and send the right sketch id to the engine.
|
||||||
|
for (const [id, artifact] of Object.entries(
|
||||||
|
engineCommandManager.artifactMap
|
||||||
|
)) {
|
||||||
|
if (artifact.commandType === 'start_path') {
|
||||||
|
guiMode.pathId = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
...guiMode,
|
...guiMode,
|
||||||
pathToNode: _pathToNode,
|
pathToNode: _pathToNode,
|
||||||
waitingFirstClick: false,
|
waitingFirstClick: false,
|
||||||
})
|
})
|
||||||
updateAst(_modifiedAst)
|
updateAst(_modifiedAst, false)
|
||||||
} else if (
|
} else if (
|
||||||
data?.data?.entities_modified?.length &&
|
resp?.data?.data?.entities_modified?.length &&
|
||||||
!guiMode.waitingFirstClick
|
(!guiMode.waitingFirstClick || isEditingExistingSketch)
|
||||||
) {
|
) {
|
||||||
const curve = await engineCommandManager?.sendSceneCommand({
|
const curve = await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'curve_get_control_points',
|
type: 'curve_get_control_points',
|
||||||
curve_id: data?.data?.entities_modified[0],
|
curve_id: resp?.data?.data?.entities_modified[0],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const coords: { x: number; y: number }[] =
|
const coords: { x: number; y: number }[] =
|
||||||
curve.data.data.control_points
|
curve.data.data.control_points
|
||||||
const _modifiedAst = addNewSketchLn({
|
|
||||||
node: ast,
|
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||||
programMemory,
|
ast,
|
||||||
to: [coords[1].x, coords[1].y],
|
guiMode.pathToNode,
|
||||||
fnName: 'line',
|
'VariableDeclarator'
|
||||||
pathToNode: guiMode.pathToNode,
|
)
|
||||||
}).modifiedAst
|
const variableName = varDec.id.name
|
||||||
updateAst(_modifiedAst)
|
const sketchGroup = programMemory.root[variableName]
|
||||||
|
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
|
||||||
|
const initialCoords = sketchGroup.value[0].from
|
||||||
|
|
||||||
|
const isClose = compareVec2Epsilon(initialCoords, [
|
||||||
|
coords[1].x,
|
||||||
|
coords[1].y,
|
||||||
|
])
|
||||||
|
|
||||||
|
let _modifiedAst: Program
|
||||||
|
if (!isClose) {
|
||||||
|
_modifiedAst = addNewSketchLn({
|
||||||
|
node: ast,
|
||||||
|
programMemory,
|
||||||
|
to: [coords[1].x, coords[1].y],
|
||||||
|
fnName: 'line',
|
||||||
|
pathToNode: guiMode.pathToNode,
|
||||||
|
}).modifiedAst
|
||||||
|
updateAst(_modifiedAst, false)
|
||||||
|
} else {
|
||||||
|
_modifiedAst = addCloseToPipe({
|
||||||
|
node: ast,
|
||||||
|
programMemory,
|
||||||
|
pathToNode: guiMode.pathToNode,
|
||||||
|
})
|
||||||
|
setGuiMode({
|
||||||
|
mode: 'default',
|
||||||
|
})
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'edit_mode_exit' },
|
||||||
|
})
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
|
})
|
||||||
|
updateAst(_modifiedAst, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setDidDragInStream(false)
|
setDidDragInStream(false)
|
||||||
@ -296,6 +460,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
onWheel={handleScroll}
|
onWheel={handleScroll}
|
||||||
onPlay={() => setIsLoading(false)}
|
onPlay={() => setIsLoading(false)}
|
||||||
onMouseMoveCapture={handleMouseMove}
|
onMouseMoveCapture={handleMouseMove}
|
||||||
|
disablePictureInPicture
|
||||||
className={`w-full h-full ${isExecuting && 'blur-md'}`}
|
className={`w-full h-full ${isExecuting && 'blur-md'}`}
|
||||||
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
||||||
/>
|
/>
|
||||||
|
@ -26,9 +26,11 @@ import {
|
|||||||
addLineHighlight,
|
addLineHighlight,
|
||||||
lineHighlightField,
|
lineHighlightField,
|
||||||
} from 'editor/highlightextension'
|
} from 'editor/highlightextension'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap, roundOff } from 'lib/utils'
|
||||||
import { kclErrToDiagnostic } from 'lang/errors'
|
import { kclErrToDiagnostic } from 'lang/errors'
|
||||||
import { CSSRuleObject } from 'tailwindcss/types/config'
|
import { CSSRuleObject } from 'tailwindcss/types/config'
|
||||||
|
import interact from '@replit/codemirror-interact'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
formatCode: {
|
formatCode: {
|
||||||
@ -49,9 +51,8 @@ export const TextEditor = ({
|
|||||||
const pathParams = useParams()
|
const pathParams = useParams()
|
||||||
const {
|
const {
|
||||||
code,
|
code,
|
||||||
defferedSetCode,
|
deferredSetCode,
|
||||||
editorView,
|
editorView,
|
||||||
engineCommandManager,
|
|
||||||
formatCode,
|
formatCode,
|
||||||
isLSPServerReady,
|
isLSPServerReady,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -59,12 +60,10 @@ export const TextEditor = ({
|
|||||||
setEditorView,
|
setEditorView,
|
||||||
setIsLSPServerReady,
|
setIsLSPServerReady,
|
||||||
setSelectionRanges,
|
setSelectionRanges,
|
||||||
sourceRangeMap,
|
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
code: s.code,
|
code: s.code,
|
||||||
defferedSetCode: s.defferedSetCode,
|
deferredSetCode: s.deferredSetCode,
|
||||||
editorView: s.editorView,
|
editorView: s.editorView,
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
formatCode: s.formatCode,
|
formatCode: s.formatCode,
|
||||||
isLSPServerReady: s.isLSPServerReady,
|
isLSPServerReady: s.isLSPServerReady,
|
||||||
selectionRanges: s.selectionRanges,
|
selectionRanges: s.selectionRanges,
|
||||||
@ -72,7 +71,6 @@ export const TextEditor = ({
|
|||||||
setEditorView: s.setEditorView,
|
setEditorView: s.setEditorView,
|
||||||
setIsLSPServerReady: s.setIsLSPServerReady,
|
setIsLSPServerReady: s.setIsLSPServerReady,
|
||||||
setSelectionRanges: s.setSelectionRanges,
|
setSelectionRanges: s.setSelectionRanges,
|
||||||
sourceRangeMap: s.sourceRangeMap,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -125,7 +123,7 @@ export const TextEditor = ({
|
|||||||
|
|
||||||
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
||||||
const onChange = (value: string, viewUpdate: ViewUpdate) => {
|
const onChange = (value: string, viewUpdate: ViewUpdate) => {
|
||||||
defferedSetCode(value)
|
deferredSetCode(value)
|
||||||
if (isTauri() && pathParams.id) {
|
if (isTauri() && pathParams.id) {
|
||||||
// Save the file to disk
|
// Save the file to disk
|
||||||
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
||||||
@ -173,11 +171,11 @@ export const TextEditor = ({
|
|||||||
)
|
)
|
||||||
const idBasedSelections = codeBasedSelections
|
const idBasedSelections = codeBasedSelections
|
||||||
.map(({ type, range }) => {
|
.map(({ type, range }) => {
|
||||||
const hasOverlap = Object.entries(sourceRangeMap).filter(
|
const hasOverlap = Object.entries(
|
||||||
([_, sourceRange]) => {
|
engineCommandManager.sourceRangeMap || {}
|
||||||
return isOverlap(sourceRange, range)
|
).filter(([_, sourceRange]) => {
|
||||||
}
|
return isOverlap(sourceRange, range)
|
||||||
)
|
})
|
||||||
if (hasOverlap.length) {
|
if (hasOverlap.length) {
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
@ -187,7 +185,7 @@ export const TextEditor = ({
|
|||||||
})
|
})
|
||||||
.filter(Boolean) as any
|
.filter(Boolean) as any
|
||||||
|
|
||||||
engineCommandManager?.cusorsSelected({
|
engineCommandManager.cusorsSelected({
|
||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
idBasedSelections,
|
idBasedSelections,
|
||||||
})
|
})
|
||||||
@ -237,6 +235,38 @@ export const TextEditor = ({
|
|||||||
lintGutter(),
|
lintGutter(),
|
||||||
linter((_view) => {
|
linter((_view) => {
|
||||||
return kclErrToDiagnostic(useStore.getState().kclErrors)
|
return kclErrToDiagnostic(useStore.getState().kclErrors)
|
||||||
|
}),
|
||||||
|
interact({
|
||||||
|
rules: [
|
||||||
|
// a rule for a number dragger
|
||||||
|
{
|
||||||
|
// the regexp matching the value
|
||||||
|
regexp: /-?\b\d+\.?\d*\b/g,
|
||||||
|
// set cursor to "ew-resize" on hover
|
||||||
|
cursor: 'ew-resize',
|
||||||
|
// change number value based on mouse X movement on drag
|
||||||
|
onDrag: (text, setText, e) => {
|
||||||
|
const multiplier =
|
||||||
|
e.shiftKey && e.metaKey
|
||||||
|
? 0.01
|
||||||
|
: e.metaKey
|
||||||
|
? 0.1
|
||||||
|
: e.shiftKey
|
||||||
|
? 10
|
||||||
|
: 1
|
||||||
|
|
||||||
|
const delta = e.movementX * multiplier
|
||||||
|
|
||||||
|
const newVal = roundOff(
|
||||||
|
Number(text) + delta,
|
||||||
|
multiplier === 0.01 ? 2 : multiplier === 0.1 ? 1 : 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isNaN(newVal)) return
|
||||||
|
setText(newVal.toString())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
if (textWrapping === 'On') extensions.push(EditorView.lineWrapping)
|
if (textWrapping === 'On') extensions.push(EditorView.lineWrapping)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import { Value, VariableDeclarator } from '../../lang/abstractSyntaxTreeTypes'
|
import { Value, VariableDeclarator } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -12,6 +12,8 @@ import {
|
|||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { updateCursors } from '../../lang/util'
|
||||||
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
|
||||||
export const EqualAngle = () => {
|
export const EqualAngle = () => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
@ -82,14 +84,22 @@ export const EqualAngle = () => {
|
|||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory,
|
||||||
})
|
})
|
||||||
updateAst(modifiedAst, {
|
updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
disabled={!enableEqual}
|
disabled={!enableEqual}
|
||||||
title="yo dawg"
|
title="Parallel (or equal angle)"
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
parallel
|
<ActionIcon
|
||||||
|
icon="parallel"
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
Parallel
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import { Value, VariableDeclarator } from '../../lang/abstractSyntaxTreeTypes'
|
import { Value, VariableDeclarator } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -12,6 +12,8 @@ import {
|
|||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { updateCursors } from '../../lang/util'
|
||||||
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
|
||||||
export const EqualLength = () => {
|
export const EqualLength = () => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
@ -82,14 +84,22 @@ export const EqualLength = () => {
|
|||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory,
|
||||||
})
|
})
|
||||||
updateAst(modifiedAst, {
|
updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
disabled={!enableEqual}
|
disabled={!enableEqual}
|
||||||
title="yo dawg"
|
className="group"
|
||||||
|
title="Equal Length"
|
||||||
>
|
>
|
||||||
EqualLength
|
<ActionIcon
|
||||||
|
icon="equal"
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
Equal Length
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import { Value } from '../../lang/abstractSyntaxTreeTypes'
|
import { Value } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -11,6 +11,8 @@ import {
|
|||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { updateCursors } from '../../lang/util'
|
||||||
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
|
||||||
export const HorzVert = ({
|
export const HorzVert = ({
|
||||||
horOrVert,
|
horOrVert,
|
||||||
@ -61,14 +63,22 @@ export const HorzVert = ({
|
|||||||
programMemory,
|
programMemory,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
updateAst(modifiedAst, {
|
updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
disabled={!enableHorz}
|
disabled={!enableHorz}
|
||||||
title="yo dawg"
|
className="group"
|
||||||
|
title={horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
||||||
>
|
>
|
||||||
{horOrVert === 'horizontal' ? 'Horz' : 'Vert'}
|
<ActionIcon
|
||||||
|
icon={horOrVert === 'horizontal' ? 'horizontal' : 'vertical'}
|
||||||
|
className="!p-0.5"
|
||||||
|
bgClassName={sketchButtonClassnames.background}
|
||||||
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
{horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { create } from 'react-modal-promise'
|
import { create } from 'react-modal-promise'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import {
|
import { BinaryPart, Value, VariableDeclarator } from '../../lang/wasm'
|
||||||
BinaryPart,
|
|
||||||
Value,
|
|
||||||
VariableDeclarator,
|
|
||||||
} from '../../lang/abstractSyntaxTreeTypes'
|
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -154,7 +150,7 @@ export const Intersect = () => {
|
|||||||
initialVariableName: 'offset',
|
initialVariableName: 'offset',
|
||||||
} as any)
|
} as any)
|
||||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||||
updateAst(modifiedAst, {
|
updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -182,14 +178,15 @@ export const Intersect = () => {
|
|||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
updateAst(_modifiedAst, {
|
updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enable}
|
disabled={!enable}
|
||||||
|
title="Set Perpendicular Distance"
|
||||||
>
|
>
|
||||||
perpendicularDistance
|
Set Perpendicular Distance
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import { Value } from '../../lang/abstractSyntaxTreeTypes'
|
import { Value } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -65,14 +65,14 @@ export const RemoveConstrainingValues = () => {
|
|||||||
programMemory,
|
programMemory,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
updateAst(modifiedAst, {
|
updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
disabled={!enableHorz}
|
disabled={!enableHorz}
|
||||||
title="yo dawg"
|
title="Remove Constraining Values"
|
||||||
>
|
>
|
||||||
RemoveConstrainingValues
|
Remove Constraining Values
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { create } from 'react-modal-promise'
|
import { create } from 'react-modal-promise'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import { Value } from '../../lang/abstractSyntaxTreeTypes'
|
import { Value } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -22,11 +22,16 @@ import { updateCursors } from '../../lang/util'
|
|||||||
|
|
||||||
const getModalInfo = create(SetAngleLengthModal as any)
|
const getModalInfo = create(SetAngleLengthModal as any)
|
||||||
|
|
||||||
export const SetAbsDistance = ({
|
type ButtonType = 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
|
||||||
buttonType,
|
|
||||||
}: {
|
const buttonLabels: Record<ButtonType, string> = {
|
||||||
buttonType: 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
|
xAbs: 'Set distance from X Axis',
|
||||||
}) => {
|
yAbs: 'Set distance from Y Axis',
|
||||||
|
snapToYAxis: 'Snap To Y Axis',
|
||||||
|
snapToXAxis: 'Snap To X Axis',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
useStore((s) => ({
|
useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
@ -124,16 +129,17 @@ export const SetAbsDistance = ({
|
|||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAst(_modifiedAst, {
|
updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('e', e)
|
console.log('error', e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enableAngLen}
|
disabled={!enableAngLen}
|
||||||
|
title={buttonLabels[buttonType]}
|
||||||
>
|
>
|
||||||
{buttonType}
|
{buttonLabels[buttonType]}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { create } from 'react-modal-promise'
|
import { create } from 'react-modal-promise'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import {
|
import { BinaryPart, Value, VariableDeclarator } from '../../lang/wasm'
|
||||||
BinaryPart,
|
|
||||||
Value,
|
|
||||||
VariableDeclarator,
|
|
||||||
} from '../../lang/abstractSyntaxTreeTypes'
|
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -113,7 +109,7 @@ export const SetAngleBetween = () => {
|
|||||||
initialVariableName: 'angle',
|
initialVariableName: 'angle',
|
||||||
} as any)
|
} as any)
|
||||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||||
updateAst(modifiedAst, {
|
updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -141,14 +137,15 @@ export const SetAngleBetween = () => {
|
|||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
updateAst(_modifiedAst, {
|
updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enable}
|
disabled={!enable}
|
||||||
|
title="Set Angle Between"
|
||||||
>
|
>
|
||||||
angleBetween
|
Set Angle Between
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { create } from 'react-modal-promise'
|
import { create } from 'react-modal-promise'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import {
|
import { BinaryPart, Value, VariableDeclarator } from '../../lang/wasm'
|
||||||
BinaryPart,
|
|
||||||
Value,
|
|
||||||
VariableDeclarator,
|
|
||||||
} from '../../lang/abstractSyntaxTreeTypes'
|
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -24,14 +20,23 @@ import { updateCursors } from '../../lang/util'
|
|||||||
|
|
||||||
const getModalInfo = create(GetInfoModal as any)
|
const getModalInfo = create(GetInfoModal as any)
|
||||||
|
|
||||||
|
type ButtonType =
|
||||||
|
| 'setHorzDistance'
|
||||||
|
| 'setVertDistance'
|
||||||
|
| 'alignEndsHorizontally'
|
||||||
|
| 'alignEndsVertically'
|
||||||
|
|
||||||
|
const buttonLabels: Record<ButtonType, string> = {
|
||||||
|
setHorzDistance: 'Set Horizontal Distance',
|
||||||
|
setVertDistance: 'Set Vertical Distance',
|
||||||
|
alignEndsHorizontally: 'Align Ends Horizontally',
|
||||||
|
alignEndsVertically: 'Align Ends Vertically',
|
||||||
|
}
|
||||||
|
|
||||||
export const SetHorzVertDistance = ({
|
export const SetHorzVertDistance = ({
|
||||||
buttonType,
|
buttonType,
|
||||||
}: {
|
}: {
|
||||||
buttonType:
|
buttonType: ButtonType
|
||||||
| 'setHorzDistance'
|
|
||||||
| 'setVertDistance'
|
|
||||||
| 'alignEndsHorizontally'
|
|
||||||
| 'alignEndsVertically'
|
|
||||||
}) => {
|
}) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
useStore((s) => ({
|
useStore((s) => ({
|
||||||
@ -137,7 +142,7 @@ export const SetHorzVertDistance = ({
|
|||||||
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
|
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
|
||||||
} as any))
|
} as any))
|
||||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||||
updateAst(modifiedAst, {
|
updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -163,14 +168,15 @@ export const SetHorzVertDistance = ({
|
|||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
updateAst(_modifiedAst, {
|
updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enable}
|
disabled={!enable}
|
||||||
|
title={buttonLabels[buttonType]}
|
||||||
>
|
>
|
||||||
{buttonType}
|
{buttonLabels[buttonType]}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { create } from 'react-modal-promise'
|
import { create } from 'react-modal-promise'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import { Value } from '../../lang/abstractSyntaxTreeTypes'
|
import { Value } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -23,10 +23,17 @@ import { updateCursors } from '../../lang/util'
|
|||||||
|
|
||||||
const getModalInfo = create(SetAngleLengthModal as any)
|
const getModalInfo = create(SetAngleLengthModal as any)
|
||||||
|
|
||||||
|
type ButtonType = 'setAngle' | 'setLength'
|
||||||
|
|
||||||
|
const buttonLabels: Record<ButtonType, string> = {
|
||||||
|
setAngle: 'Set Angle',
|
||||||
|
setLength: 'Set Length',
|
||||||
|
}
|
||||||
|
|
||||||
export const SetAngleLength = ({
|
export const SetAngleLength = ({
|
||||||
angleOrLength,
|
angleOrLength,
|
||||||
}: {
|
}: {
|
||||||
angleOrLength: 'setAngle' | 'setLength'
|
angleOrLength: ButtonType
|
||||||
}) => {
|
}) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||||
useStore((s) => ({
|
useStore((s) => ({
|
||||||
@ -136,16 +143,17 @@ export const SetAngleLength = ({
|
|||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAst(_modifiedAst, {
|
updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('e', e)
|
console.log('erorr', e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!enableAngLen}
|
disabled={!enableAngLen}
|
||||||
|
title={buttonLabels[angleOrLength]}
|
||||||
>
|
>
|
||||||
{angleOrLength}
|
{buttonLabels[angleOrLength]}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import UserSidebarMenu from './UserSidebarMenu'
|
import UserSidebarMenu from './UserSidebarMenu'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import {
|
||||||
|
Route,
|
||||||
|
RouterProvider,
|
||||||
|
createMemoryRouter,
|
||||||
|
createRoutesFromElements,
|
||||||
|
} from 'react-router-dom'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { GlobalStateProvider } from './GlobalStateProvider'
|
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||||
import CommandBarProvider from './CommandBar'
|
import CommandBarProvider from './CommandBar'
|
||||||
@ -93,11 +98,24 @@ describe('UserSidebarMenu tests', () => {
|
|||||||
|
|
||||||
function TestWrap({ children }: { children: React.ReactNode }) {
|
function TestWrap({ children }: { children: React.ReactNode }) {
|
||||||
// wrap in router and xState context
|
// wrap in router and xState context
|
||||||
return (
|
// We have to use a memory router in the testing environment,
|
||||||
<BrowserRouter>
|
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
|
||||||
<CommandBarProvider>
|
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
|
||||||
<GlobalStateProvider>{children}</GlobalStateProvider>
|
const router = createMemoryRouter(
|
||||||
</CommandBarProvider>
|
createRoutesFromElements(
|
||||||
</BrowserRouter>
|
<Route
|
||||||
|
path="/file/:id"
|
||||||
|
element={
|
||||||
|
<CommandBarProvider>
|
||||||
|
<GlobalStateProvider>{children}</GlobalStateProvider>
|
||||||
|
</CommandBarProvider>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
initialEntries: ['/file/new'],
|
||||||
|
initialIndex: 0,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
return <RouterProvider router={router} />
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import { Popover, Transition } from '@headlessui/react'
|
import { Popover, Transition } from '@headlessui/react'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
import {
|
||||||
|
faBars,
|
||||||
|
faBug,
|
||||||
|
faGear,
|
||||||
|
faSignOutAlt,
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { paths } from '../Router'
|
import { paths } from '../Router'
|
||||||
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
|
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
|
|
||||||
type User = Models['User_type']
|
type User = Models['User_type']
|
||||||
|
|
||||||
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||||
|
const location = useLocation()
|
||||||
|
const filePath = useAbsoluteFilePath()
|
||||||
const displayedName = getDisplayName(user)
|
const displayedName = getDisplayName(user)
|
||||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -38,7 +45,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
{user?.image && !imageLoadFailed ? (
|
{user?.image && !imageLoadFailed ? (
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className="border-0 rounded-full w-fit p-0 focus:outline-none group"
|
className="border-0 rounded-full w-fit min-w-max p-0 focus:outline-none group"
|
||||||
data-testid="user-sidebar-toggle"
|
data-testid="user-sidebar-toggle"
|
||||||
>
|
>
|
||||||
<div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 group-focus:border-liquid-50 overflow-hidden">
|
<div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 group-focus:border-liquid-50 overflow-hidden">
|
||||||
@ -126,19 +133,30 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
// since /settings is a nested route the sidebar doesn't close
|
// since /settings is a nested route the sidebar doesn't close
|
||||||
// automatically when navigating to it
|
// automatically when navigating to it
|
||||||
close()
|
close()
|
||||||
navigate(makeUrlPathRelative(paths.SETTINGS))
|
const targetPath = location.pathname.includes(paths.FILE)
|
||||||
|
? filePath + paths.SETTINGS
|
||||||
|
: paths.HOME + paths.SETTINGS
|
||||||
|
navigate(targetPath)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="link"
|
Element="externalLink"
|
||||||
to="https://github.com/KittyCAD/modeling-app/discussions"
|
to="https://github.com/KittyCAD/modeling-app/discussions"
|
||||||
icon={{ icon: faGithub }}
|
icon={{ icon: faGithub }}
|
||||||
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
|
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
|
||||||
>
|
>
|
||||||
Request a feature
|
Request a feature
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="externalLink"
|
||||||
|
to="https://github.com/KittyCAD/modeling-app/issues/new"
|
||||||
|
icon={{ icon: faBug }}
|
||||||
|
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
|
||||||
|
>
|
||||||
|
Report a bug
|
||||||
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send('Log out')}
|
onClick={() => send('Log out')}
|
||||||
|
@ -109,7 +109,6 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
messageString += message
|
messageString += message
|
||||||
// console.log(messageString)
|
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
CompletionItemKind,
|
CompletionItemKind,
|
||||||
CompletionTriggerKind,
|
CompletionTriggerKind,
|
||||||
} from 'vscode-languageserver-protocol'
|
} from 'vscode-languageserver-protocol'
|
||||||
|
import debounce from 'debounce-promise'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Completion,
|
Completion,
|
||||||
@ -53,14 +54,11 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
private languageId: string
|
private languageId: string
|
||||||
private documentVersion: number
|
private documentVersion: number
|
||||||
|
|
||||||
private changesTimeout: number
|
|
||||||
|
|
||||||
constructor(private view: EditorView, private allowHTMLContent: boolean) {
|
constructor(private view: EditorView, private allowHTMLContent: boolean) {
|
||||||
this.client = this.view.state.facet(client)
|
this.client = this.view.state.facet(client)
|
||||||
this.documentUri = this.view.state.facet(documentUri)
|
this.documentUri = this.view.state.facet(documentUri)
|
||||||
this.languageId = this.view.state.facet(languageId)
|
this.languageId = this.view.state.facet(languageId)
|
||||||
this.documentVersion = 0
|
this.documentVersion = 0
|
||||||
this.changesTimeout = 0
|
|
||||||
|
|
||||||
this.client.attachPlugin(this)
|
this.client.attachPlugin(this)
|
||||||
|
|
||||||
@ -71,12 +69,10 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
|
|
||||||
update({ docChanged }: ViewUpdate) {
|
update({ docChanged }: ViewUpdate) {
|
||||||
if (!docChanged) return
|
if (!docChanged) return
|
||||||
if (this.changesTimeout) clearTimeout(this.changesTimeout)
|
|
||||||
this.changesTimeout = window.setTimeout(() => {
|
this.sendChange({
|
||||||
this.sendChange({
|
documentText: this.view.state.doc.toString(),
|
||||||
documentText: this.view.state.doc.toString(),
|
})
|
||||||
})
|
|
||||||
}, changesDelay)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -99,14 +95,32 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
|
|
||||||
async sendChange({ documentText }: { documentText: string }) {
|
async sendChange({ documentText }: { documentText: string }) {
|
||||||
if (!this.client.ready) return
|
if (!this.client.ready) return
|
||||||
|
|
||||||
|
if (documentText.length > 5000) {
|
||||||
|
// Clear out the text it thinks we have, large documents will throw a stack error.
|
||||||
|
// This is obviously not a good fix but it works for now til we figure
|
||||||
|
// out the stack limits in wasm and also rewrite the parser.
|
||||||
|
// Since this is only for hover and completions it will be fine,
|
||||||
|
// completions will still work for stdlib but hover will not.
|
||||||
|
// That seems like a fine trade-off for a working editor for the time
|
||||||
|
// being.
|
||||||
|
documentText = ''
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.textDocumentDidChange({
|
debounce(
|
||||||
textDocument: {
|
() => {
|
||||||
uri: this.documentUri,
|
return this.client.textDocumentDidChange({
|
||||||
version: this.documentVersion++,
|
textDocument: {
|
||||||
|
uri: this.documentUri,
|
||||||
|
version: this.documentVersion++,
|
||||||
|
},
|
||||||
|
contentChanges: [{ text: documentText }],
|
||||||
|
})
|
||||||
},
|
},
|
||||||
contentChanges: [{ text: documentText }],
|
changesDelay,
|
||||||
})
|
{ leading: true }
|
||||||
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import init, {
|
|||||||
} from '../../wasm-lib/pkg/wasm_lib'
|
} from '../../wasm-lib/pkg/wasm_lib'
|
||||||
import { FromServer, IntoServer } from './codec'
|
import { FromServer, IntoServer } from './codec'
|
||||||
|
|
||||||
let server: null | Server
|
|
||||||
|
|
||||||
export default class Server {
|
export default class Server {
|
||||||
readonly initOutput: InitOutput
|
readonly initOutput: InitOutput
|
||||||
readonly #intoServer: IntoServer
|
readonly #intoServer: IntoServer
|
||||||
@ -26,12 +24,8 @@ export default class Server {
|
|||||||
intoServer: IntoServer,
|
intoServer: IntoServer,
|
||||||
fromServer: FromServer
|
fromServer: FromServer
|
||||||
): Promise<Server> {
|
): Promise<Server> {
|
||||||
if (null == server) {
|
const initOutput = await init()
|
||||||
const initOutput = await init()
|
const server = new Server(initOutput, intoServer, fromServer)
|
||||||
server = new Server(initOutput, intoServer, fromServer)
|
|
||||||
} else {
|
|
||||||
console.warn('Server already initialized; ignoring')
|
|
||||||
}
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
src/hooks/useAbsoluteFilePath.ts
Normal file
12
src/hooks/useAbsoluteFilePath.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { BROWSER_FILE_NAME, IndexLoaderData, paths } from 'Router'
|
||||||
|
import { useRouteLoaderData } from 'react-router-dom'
|
||||||
|
|
||||||
|
export function useAbsoluteFilePath() {
|
||||||
|
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||||
|
|
||||||
|
return (
|
||||||
|
paths.FILE +
|
||||||
|
'/' +
|
||||||
|
encodeURIComponent(routeData?.project?.path || BROWSER_FILE_NAME)
|
||||||
|
)
|
||||||
|
}
|
@ -2,74 +2,138 @@
|
|||||||
// Once we have xState this should be removed
|
// Once we have xState this should be removed
|
||||||
|
|
||||||
import { useStore, Selections } from 'useStore'
|
import { useStore, Selections } from 'useStore'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
|
import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import { isReducedMotion } from 'lang/util'
|
import { isReducedMotion } from 'lang/util'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
interface DefaultPlanes {
|
import { DefaultPlanes } from '../wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
xy: string
|
import { getNodeFromPath } from '../lang/queryAst'
|
||||||
yz: string
|
import { CallExpression, PipeExpression } from '../lang/wasm'
|
||||||
xz: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAppMode() {
|
export function useAppMode() {
|
||||||
const {
|
const {
|
||||||
guiMode,
|
guiMode,
|
||||||
setGuiMode,
|
setGuiMode,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
engineCommandManager,
|
|
||||||
selectionRangeTypeMap,
|
selectionRangeTypeMap,
|
||||||
|
defaultPlanes,
|
||||||
|
setDefaultPlanes,
|
||||||
|
setCurrentPlane,
|
||||||
|
ast,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
setGuiMode: s.setGuiMode,
|
setGuiMode: s.setGuiMode,
|
||||||
selectionRanges: s.selectionRanges,
|
selectionRanges: s.selectionRanges,
|
||||||
engineCommandManager: s.engineCommandManager,
|
|
||||||
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
||||||
|
defaultPlanes: s.defaultPlanes,
|
||||||
|
setDefaultPlanes: s.setDefaultPlanes,
|
||||||
|
setCurrentPlane: s.setCurrentPlane,
|
||||||
|
ast: s.ast,
|
||||||
}))
|
}))
|
||||||
const [defaultPlanes, setDefaultPlanes] = useState<DefaultPlanes | null>(null)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
guiMode.mode === 'sketch' &&
|
guiMode.mode === 'sketch' &&
|
||||||
guiMode.sketchMode === 'selectFace' &&
|
guiMode.sketchMode === 'selectFace' &&
|
||||||
engineCommandManager
|
engineCommandManager
|
||||||
) {
|
) {
|
||||||
if (!defaultPlanes) {
|
const createAndShowPlanes = async () => {
|
||||||
const xy = createPlane(engineCommandManager, {
|
let localDefaultPlanes: DefaultPlanes
|
||||||
x_axis: { x: 1, y: 0, z: 0 },
|
if (!defaultPlanes) {
|
||||||
y_axis: { x: 0, y: 1, z: 0 },
|
const newDefaultPlanes = await initDefaultPlanes(engineCommandManager)
|
||||||
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
if (!newDefaultPlanes) return
|
||||||
})
|
setDefaultPlanes(newDefaultPlanes)
|
||||||
const yz = createPlane(engineCommandManager, {
|
localDefaultPlanes = newDefaultPlanes
|
||||||
x_axis: { x: 0, y: 1, z: 0 },
|
} else {
|
||||||
y_axis: { x: 0, y: 0, z: 1 },
|
localDefaultPlanes = defaultPlanes
|
||||||
color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
}
|
||||||
})
|
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
|
||||||
const xz = createPlane(engineCommandManager, {
|
|
||||||
x_axis: { x: 1, y: 0, z: 0 },
|
|
||||||
y_axis: { x: 0, y: 0, z: 1 },
|
|
||||||
color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
|
||||||
})
|
|
||||||
setDefaultPlanes({ xy, yz, xz })
|
|
||||||
} else {
|
|
||||||
hideDefaultPlanes(engineCommandManager, defaultPlanes)
|
|
||||||
}
|
}
|
||||||
|
createAndShowPlanes()
|
||||||
}
|
}
|
||||||
if (guiMode.mode !== 'sketch' && defaultPlanes) {
|
if (
|
||||||
Object.values(defaultPlanes).forEach((planeId) => {
|
guiMode.mode === 'sketch' &&
|
||||||
engineCommandManager?.sendSceneCommand({
|
guiMode.sketchMode === 'enterSketchEdit' &&
|
||||||
|
engineCommandManager
|
||||||
|
) {
|
||||||
|
const enableSketchMode = async () => {
|
||||||
|
let localDefaultPlanes: DefaultPlanes
|
||||||
|
if (!defaultPlanes) {
|
||||||
|
const newDefaultPlanes = await initDefaultPlanes(engineCommandManager)
|
||||||
|
if (!newDefaultPlanes) return
|
||||||
|
setDefaultPlanes(newDefaultPlanes)
|
||||||
|
localDefaultPlanes = newDefaultPlanes
|
||||||
|
} else {
|
||||||
|
localDefaultPlanes = defaultPlanes
|
||||||
|
}
|
||||||
|
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, true)
|
||||||
|
|
||||||
|
const pipeExpression = getNodeFromPath<PipeExpression>(
|
||||||
|
ast,
|
||||||
|
guiMode.pathToNode,
|
||||||
|
'PipeExpression'
|
||||||
|
).node
|
||||||
|
if (pipeExpression.type !== 'PipeExpression') return /// bad bad bad
|
||||||
|
const sketchCallExpression = pipeExpression.body.find(
|
||||||
|
(e) =>
|
||||||
|
e.type === 'CallExpression' && e.callee.name === 'startSketchOn'
|
||||||
|
) as CallExpression
|
||||||
|
if (!sketchCallExpression) return // also bad bad bad
|
||||||
|
const firstArg = sketchCallExpression.arguments[0]
|
||||||
|
let planeId = ''
|
||||||
|
if (firstArg.type === 'Literal' && firstArg.value) {
|
||||||
|
const planeStrCleaned = firstArg.value
|
||||||
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace('-', '')
|
||||||
|
if (
|
||||||
|
planeStrCleaned === 'xy' ||
|
||||||
|
planeStrCleaned === 'xz' ||
|
||||||
|
planeStrCleaned === 'yz'
|
||||||
|
) {
|
||||||
|
planeId = localDefaultPlanes[planeStrCleaned]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!planeId) return // they are on some non default plane, which we don't support yet
|
||||||
|
|
||||||
|
setCurrentPlane(planeId)
|
||||||
|
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'object_visible',
|
type: 'sketch_mode_enable',
|
||||||
object_id: planeId,
|
plane_id: planeId,
|
||||||
hidden: true,
|
ortho: true,
|
||||||
|
animated: !isReducedMotion(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
const proms: any[] = []
|
||||||
|
proms.push(
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'edit_mode_enter',
|
||||||
|
target: guiMode.pathId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await Promise.all(proms)
|
||||||
|
}
|
||||||
|
enableSketchMode()
|
||||||
|
setGuiMode({
|
||||||
|
...guiMode,
|
||||||
|
sketchMode: 'sketchEdit',
|
||||||
})
|
})
|
||||||
} else if (guiMode.mode === 'default') {
|
}
|
||||||
|
if (guiMode.mode !== 'sketch' && defaultPlanes) {
|
||||||
|
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
||||||
|
}
|
||||||
|
if (guiMode.mode === 'default') {
|
||||||
const pathId =
|
const pathId =
|
||||||
engineCommandManager &&
|
engineCommandManager &&
|
||||||
isCursorInSketchCommandRange(
|
isCursorInSketchCommandRange(
|
||||||
@ -107,7 +171,7 @@ export function useAppMode() {
|
|||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unSub = engineCommandManager?.subscribeTo({
|
const unSub = engineCommandManager.subscribeTo({
|
||||||
event: 'select_with_point',
|
event: 'select_with_point',
|
||||||
callback: async ({ data }) => {
|
callback: async ({ data }) => {
|
||||||
if (!data.entity_id) return
|
if (!data.entity_id) return
|
||||||
@ -116,19 +180,18 @@ export function useAppMode() {
|
|||||||
// user clicked something else in the scene
|
// user clicked something else in the scene
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const sketchModeResponse = await engineCommandManager?.sendSceneCommand(
|
setCurrentPlane(data.entity_id)
|
||||||
{
|
const sketchModeResponse = await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'sketch_mode_enable',
|
type: 'sketch_mode_enable',
|
||||||
plane_id: data.entity_id,
|
plane_id: data.entity_id,
|
||||||
ortho: true,
|
ortho: true,
|
||||||
animated: !isReducedMotion(),
|
animated: !isReducedMotion(),
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
)
|
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
||||||
hideDefaultPlanes(engineCommandManager, defaultPlanes)
|
|
||||||
const sketchUuid = uuidv4()
|
const sketchUuid = uuidv4()
|
||||||
const proms: any[] = []
|
const proms: any[] = []
|
||||||
proms.push(
|
proms.push(
|
||||||
@ -150,14 +213,14 @@ export function useAppMode() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const res = await Promise.all(proms)
|
await Promise.all(proms)
|
||||||
console.log('res', res)
|
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'sketchEdit',
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
pathToNode: [],
|
pathToNode: [],
|
||||||
|
pathId: sketchUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('sketchModeResponse', sketchModeResponse)
|
console.log('sketchModeResponse', sketchModeResponse)
|
||||||
@ -167,20 +230,22 @@ export function useAppMode() {
|
|||||||
}, [engineCommandManager, defaultPlanes])
|
}, [engineCommandManager, defaultPlanes])
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPlane(
|
async function createPlane(
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
{
|
{
|
||||||
x_axis,
|
x_axis,
|
||||||
y_axis,
|
y_axis,
|
||||||
color,
|
color,
|
||||||
|
hidden,
|
||||||
}: {
|
}: {
|
||||||
x_axis: Models['Point3d_type']
|
x_axis: Models['Point3d_type']
|
||||||
y_axis: Models['Point3d_type']
|
y_axis: Models['Point3d_type']
|
||||||
color: Models['Color_type']
|
color: Models['Color_type']
|
||||||
|
hidden: boolean
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const planeId = uuidv4()
|
const planeId = uuidv4()
|
||||||
engineCommandManager?.sendSceneCommand({
|
await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'make_plane',
|
type: 'make_plane',
|
||||||
@ -189,10 +254,11 @@ function createPlane(
|
|||||||
x_axis,
|
x_axis,
|
||||||
y_axis,
|
y_axis,
|
||||||
clobber: false,
|
clobber: false,
|
||||||
|
hide: hidden,
|
||||||
},
|
},
|
||||||
cmd_id: planeId,
|
cmd_id: planeId,
|
||||||
})
|
})
|
||||||
engineCommandManager?.sendSceneCommand({
|
await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'plane_set_color',
|
type: 'plane_set_color',
|
||||||
@ -204,40 +270,86 @@ function createPlane(
|
|||||||
return planeId
|
return planeId
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideDefaultPlanes(
|
export function setDefaultPlanesHidden(
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
defaultPlanes: DefaultPlanes
|
defaultPlanes: DefaultPlanes,
|
||||||
|
hidden: boolean
|
||||||
) {
|
) {
|
||||||
Object.values(defaultPlanes).forEach((planeId) => {
|
Object.values(defaultPlanes).forEach((planeId) => {
|
||||||
engineCommandManager?.sendSceneCommand({
|
hidePlane(engineCommandManager, planeId, hidden)
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'object_visible',
|
|
||||||
object_id: planeId,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hidePlane(
|
||||||
|
engineCommandManager: EngineCommandManager,
|
||||||
|
planeId: string,
|
||||||
|
hidden: boolean
|
||||||
|
) {
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'object_visible',
|
||||||
|
object_id: planeId,
|
||||||
|
hidden: hidden,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initDefaultPlanes(
|
||||||
|
engineCommandManager: EngineCommandManager,
|
||||||
|
hidePlanes?: boolean
|
||||||
|
): Promise<DefaultPlanes | null> {
|
||||||
|
if (!engineCommandManager.engineConnection?.isReady()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const xy = await createPlane(engineCommandManager, {
|
||||||
|
x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
||||||
|
hidden: hidePlanes ? true : false,
|
||||||
|
})
|
||||||
|
if (hidePlanes) {
|
||||||
|
hidePlane(engineCommandManager, xy, true)
|
||||||
|
}
|
||||||
|
const yz = await createPlane(engineCommandManager, {
|
||||||
|
x_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
||||||
|
hidden: hidePlanes ? true : false,
|
||||||
|
})
|
||||||
|
if (hidePlanes) {
|
||||||
|
hidePlane(engineCommandManager, yz, true)
|
||||||
|
}
|
||||||
|
const xz = await createPlane(engineCommandManager, {
|
||||||
|
x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
||||||
|
hidden: hidePlanes ? true : false,
|
||||||
|
})
|
||||||
|
return { xy, yz, xz }
|
||||||
|
}
|
||||||
|
|
||||||
function isCursorInSketchCommandRange(
|
function isCursorInSketchCommandRange(
|
||||||
artifactMap: ArtifactMap,
|
artifactMap: ArtifactMap,
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
): string | false {
|
): string | false {
|
||||||
const overlapingEntries = Object.entries(artifactMap || {}).filter(
|
const overlapingEntries: [string, ArtifactMap[string]][] = Object.entries(
|
||||||
([id, artifact]) =>
|
artifactMap
|
||||||
selectionRanges.codeBasedSelections.some(
|
).filter(([id, artifact]: [string, ArtifactMap[string]]) =>
|
||||||
(selection) =>
|
selectionRanges.codeBasedSelections.some(
|
||||||
Array.isArray(selection.range) &&
|
(selection) =>
|
||||||
Array.isArray(artifact.range) &&
|
Array.isArray(selection?.range) &&
|
||||||
isOverlap(selection.range, artifact.range) &&
|
Array.isArray(artifact?.range) &&
|
||||||
(artifact.commandType === 'start_path' ||
|
isOverlap(selection.range, artifact.range) &&
|
||||||
artifact.commandType === 'extend_path' ||
|
(artifact.commandType === 'start_path' ||
|
||||||
'close_path')
|
artifact.commandType === 'extend_path' ||
|
||||||
)
|
artifact.commandType === 'close_path')
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return overlapingEntries.length === 1 && overlapingEntries[0][1].parentId
|
return overlapingEntries.length && overlapingEntries[0][1].parentId
|
||||||
? overlapingEntries[0][1].parentId
|
? overlapingEntries[0][1].parentId
|
||||||
: false
|
: overlapingEntries.find(
|
||||||
|
([, artifact]) => artifact.commandType === 'start_path'
|
||||||
|
)?.[0] || false
|
||||||
}
|
}
|
||||||
|
13
src/hooks/useDotDotSlash.ts
Normal file
13
src/hooks/useDotDotSlash.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
|
export function useDotDotSlash(): (count?: number) => string {
|
||||||
|
const location = useLocation()
|
||||||
|
const dotDotSlash = (count = 1): string => {
|
||||||
|
// since we can't use relative paths (../) for windows
|
||||||
|
if (location.pathname === '/') return ''
|
||||||
|
const path = location.pathname.slice(0, location.pathname.lastIndexOf('/'))
|
||||||
|
if (count <= 1) return path
|
||||||
|
return dotDotSlash(count - 1)
|
||||||
|
}
|
||||||
|
return dotDotSlash
|
||||||
|
}
|
45
src/hooks/useEngineConnectionSubscriptions.ts
Normal file
45
src/hooks/useEngineConnectionSubscriptions.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
|
||||||
|
export function useEngineConnectionSubscriptions() {
|
||||||
|
const { setCursor2, setHighlightRange, highlightRange } = useStore((s) => ({
|
||||||
|
setCursor2: s.setCursor2,
|
||||||
|
setHighlightRange: s.setHighlightRange,
|
||||||
|
highlightRange: s.highlightRange,
|
||||||
|
}))
|
||||||
|
useEffect(() => {
|
||||||
|
if (!engineCommandManager) return
|
||||||
|
|
||||||
|
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
||||||
|
event: 'highlight_set_entity',
|
||||||
|
callback: ({ data }) => {
|
||||||
|
if (data?.entity_id) {
|
||||||
|
const sourceRange =
|
||||||
|
engineCommandManager.sourceRangeMap[data.entity_id]
|
||||||
|
setHighlightRange(sourceRange)
|
||||||
|
} else if (
|
||||||
|
!highlightRange ||
|
||||||
|
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
|
||||||
|
) {
|
||||||
|
setHighlightRange([0, 0])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const unSubClick = engineCommandManager.subscribeTo({
|
||||||
|
event: 'select_with_point',
|
||||||
|
callback: ({ data }) => {
|
||||||
|
if (!data?.entity_id) {
|
||||||
|
setCursor2()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
|
||||||
|
setCursor2({ range: sourceRange, type: 'default' })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
unSubHover()
|
||||||
|
unSubClick()
|
||||||
|
}
|
||||||
|
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange])
|
||||||
|
}
|
92
src/hooks/useSetupEngineManager.ts
Normal file
92
src/hooks/useSetupEngineManager.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { useLayoutEffect, useEffect, useRef } from 'react'
|
||||||
|
import { _executor } from '../lang/wasm'
|
||||||
|
import { useStore } from '../useStore'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
import { deferExecution } from 'lib/utils'
|
||||||
|
|
||||||
|
export function useSetupEngineManager(
|
||||||
|
streamRef: React.RefObject<HTMLDivElement>,
|
||||||
|
token?: string
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
setMediaStream,
|
||||||
|
setIsStreamReady,
|
||||||
|
setStreamDimensions,
|
||||||
|
streamDimensions,
|
||||||
|
executeCode,
|
||||||
|
} = useStore((s) => ({
|
||||||
|
setMediaStream: s.setMediaStream,
|
||||||
|
setIsStreamReady: s.setIsStreamReady,
|
||||||
|
setStreamDimensions: s.setStreamDimensions,
|
||||||
|
streamDimensions: s.streamDimensions,
|
||||||
|
executeCode: s.executeCode,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const streamWidth = streamRef?.current?.offsetWidth
|
||||||
|
const streamHeight = streamRef?.current?.offsetHeight
|
||||||
|
|
||||||
|
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
executeCode()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
|
||||||
|
engineCommandManager.start({
|
||||||
|
setMediaStream,
|
||||||
|
setIsStreamReady,
|
||||||
|
width: quadWidth,
|
||||||
|
height: quadHeight,
|
||||||
|
executeCode,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
setStreamDimensions({
|
||||||
|
streamWidth: quadWidth,
|
||||||
|
streamHeight: quadHeight,
|
||||||
|
})
|
||||||
|
hasSetNonZeroDimensions.current = true
|
||||||
|
}
|
||||||
|
}, [streamRef?.current?.offsetWidth, streamRef?.current?.offsetHeight])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = deferExecution(() => {
|
||||||
|
const { width, height } = getDimensions(
|
||||||
|
streamRef?.current?.offsetWidth,
|
||||||
|
streamRef?.current?.offsetHeight
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
streamDimensions.streamWidth !== width ||
|
||||||
|
streamDimensions.streamHeight !== height
|
||||||
|
) {
|
||||||
|
engineCommandManager.handleResize({
|
||||||
|
streamWidth: width,
|
||||||
|
streamHeight: height,
|
||||||
|
})
|
||||||
|
setStreamDimensions({
|
||||||
|
streamWidth: width,
|
||||||
|
streamHeight: height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDimensions(streamWidth?: number, streamHeight?: number) {
|
||||||
|
const width = streamWidth ? streamWidth : 0
|
||||||
|
const quadWidth = Math.round(width / 4) * 4
|
||||||
|
const height = streamHeight ? streamHeight : 0
|
||||||
|
const quadHeight = Math.round(height / 4) * 4
|
||||||
|
return { width: quadWidth, height: quadHeight }
|
||||||
|
}
|
@ -46,9 +46,9 @@ export function useConvertToVariable() {
|
|||||||
variableName
|
variableName
|
||||||
)
|
)
|
||||||
|
|
||||||
updateAst(_modifiedAst)
|
updateAst(_modifiedAst, true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('e', e)
|
console.log('error', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,14 @@ body.dark {
|
|||||||
@apply text-chalkboard-10;
|
@apply text-chalkboard-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
@apply bg-chalkboard-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark select {
|
||||||
|
@apply bg-chalkboard-90;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@apply w-2 h-2 rounded-sm;
|
@apply w-2 h-2 rounded-sm;
|
||||||
@apply bg-chalkboard-20;
|
@apply bg-chalkboard-20;
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { KCLError } from './errors'
|
||||||
import { KCLUnexpectedError } from './errors'
|
import { initPromise, parse } from './wasm'
|
||||||
import { initPromise } from './rust'
|
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
describe('testing AST', () => {
|
describe('testing AST', () => {
|
||||||
test('5 + 6', () => {
|
test('5 + 6', () => {
|
||||||
const result = parser_wasm('5 +6')
|
const result = parse('5 +6')
|
||||||
delete (result as any).nonCodeMeta
|
delete (result as any).nonCodeMeta
|
||||||
expect(result.body).toEqual([
|
expect(result.body).toEqual([
|
||||||
{
|
{
|
||||||
@ -37,7 +36,7 @@ describe('testing AST', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('const myVar = 5', () => {
|
test('const myVar = 5', () => {
|
||||||
const { body } = parser_wasm('const myVar = 5')
|
const { body } = parse('const myVar = 5')
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -71,7 +70,7 @@ describe('testing AST', () => {
|
|||||||
const code = `const myVar = 5
|
const code = `const myVar = 5
|
||||||
const newVar = myVar + 1
|
const newVar = myVar + 1
|
||||||
`
|
`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -139,59 +138,11 @@ const newVar = myVar + 1
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('using std function "log"', () => {
|
|
||||||
const code = `log(5, "hello", aIdentifier)`
|
|
||||||
const { body } = parser_wasm(code)
|
|
||||||
expect(body).toEqual([
|
|
||||||
{
|
|
||||||
type: 'ExpressionStatement',
|
|
||||||
start: 0,
|
|
||||||
end: 28,
|
|
||||||
expression: {
|
|
||||||
type: 'CallExpression',
|
|
||||||
start: 0,
|
|
||||||
end: 28,
|
|
||||||
callee: {
|
|
||||||
type: 'Identifier',
|
|
||||||
start: 0,
|
|
||||||
end: 3,
|
|
||||||
name: 'log',
|
|
||||||
},
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
type: 'Literal',
|
|
||||||
start: 4,
|
|
||||||
end: 5,
|
|
||||||
value: 5,
|
|
||||||
raw: '5',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'Literal',
|
|
||||||
start: 7,
|
|
||||||
end: 14,
|
|
||||||
value: 'hello',
|
|
||||||
raw: '"hello"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'Identifier',
|
|
||||||
start: 16,
|
|
||||||
end: 27,
|
|
||||||
name: 'aIdentifier',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
function: {
|
|
||||||
type: 'InMemory',
|
|
||||||
},
|
|
||||||
optional: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('testing function declaration', () => {
|
describe('testing function declaration', () => {
|
||||||
test('fn funcN = () => {}', () => {
|
test('fn funcN = () => {}', () => {
|
||||||
const { body } = parser_wasm('fn funcN = () => {}')
|
const { body } = parse('fn funcN = () => {}')
|
||||||
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
@ -227,7 +178,7 @@ describe('testing function declaration', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('fn funcN = (a, b) => {return a + b}', () => {
|
test('fn funcN = (a, b) => {return a + b}', () => {
|
||||||
const { body } = parser_wasm(
|
const { body } = parse(
|
||||||
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
|
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
|
||||||
)
|
)
|
||||||
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
||||||
@ -304,7 +255,7 @@ describe('testing function declaration', () => {
|
|||||||
test('call expression assignment', () => {
|
test('call expression assignment', () => {
|
||||||
const code = `fn funcN = (a, b) => { return a + b }
|
const code = `fn funcN = (a, b) => { return a + b }
|
||||||
const myVar = funcN(1, 2)`
|
const myVar = funcN(1, 2)`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
@ -436,7 +387,7 @@ describe('testing pipe operator special', () => {
|
|||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> rx(45, %)
|
|> rx(45, %)
|
||||||
`
|
`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
delete (body[0] as any).declarations[0].init.nonCodeMeta
|
delete (body[0] as any).declarations[0].init.nonCodeMeta
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
@ -452,7 +403,7 @@ describe('testing pipe operator special', () => {
|
|||||||
id: { type: 'Identifier', start: 6, end: 14, name: 'mySketch' },
|
id: { type: 'Identifier', start: 6, end: 14, name: 'mySketch' },
|
||||||
init: {
|
init: {
|
||||||
type: 'PipeExpression',
|
type: 'PipeExpression',
|
||||||
start: 15,
|
start: 17,
|
||||||
end: 145,
|
end: 145,
|
||||||
body: [
|
body: [
|
||||||
{
|
{
|
||||||
@ -672,7 +623,7 @@ describe('testing pipe operator special', () => {
|
|||||||
})
|
})
|
||||||
test('pipe operator with binary expression', () => {
|
test('pipe operator with binary expression', () => {
|
||||||
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
|
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
delete (body as any)[0].declarations[0].init.nonCodeMeta
|
delete (body as any)[0].declarations[0].init.nonCodeMeta
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
@ -693,7 +644,7 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
init: {
|
init: {
|
||||||
type: 'PipeExpression',
|
type: 'PipeExpression',
|
||||||
start: 12,
|
start: 14,
|
||||||
end: 36,
|
end: 36,
|
||||||
body: [
|
body: [
|
||||||
{
|
{
|
||||||
@ -754,7 +705,7 @@ describe('testing pipe operator special', () => {
|
|||||||
})
|
})
|
||||||
test('array expression', () => {
|
test('array expression', () => {
|
||||||
let code = `const yo = [1, '2', three, 4 + 5]`
|
let code = `const yo = [1, '2', three, 4 + 5]`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -829,7 +780,7 @@ describe('testing pipe operator special', () => {
|
|||||||
'const three = 3',
|
'const three = 3',
|
||||||
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -973,7 +924,7 @@ describe('testing pipe operator special', () => {
|
|||||||
const code = `const yo = {key: {
|
const code = `const yo = {key: {
|
||||||
key2: 'value'
|
key2: 'value'
|
||||||
}}`
|
}}`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -1041,7 +992,7 @@ describe('testing pipe operator special', () => {
|
|||||||
})
|
})
|
||||||
test('object expression with array ast', () => {
|
test('object expression with array ast', () => {
|
||||||
const code = `const yo = {key: [1, '2']}`
|
const code = `const yo = {key: [1, '2']}`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -1105,7 +1056,7 @@ describe('testing pipe operator special', () => {
|
|||||||
})
|
})
|
||||||
test('object memberExpression simple', () => {
|
test('object memberExpression simple', () => {
|
||||||
const code = `const prop = yo.one.two`
|
const code = `const prop = yo.one.two`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -1160,7 +1111,7 @@ describe('testing pipe operator special', () => {
|
|||||||
})
|
})
|
||||||
test('object memberExpression with square braces', () => {
|
test('object memberExpression with square braces', () => {
|
||||||
const code = `const prop = yo.one["two"]`
|
const code = `const prop = yo.one["two"]`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -1216,7 +1167,7 @@ describe('testing pipe operator special', () => {
|
|||||||
})
|
})
|
||||||
test('object memberExpression with two square braces literal and identifier', () => {
|
test('object memberExpression with two square braces literal and identifier', () => {
|
||||||
const code = `const prop = yo["one"][two]`
|
const code = `const prop = yo["one"][two]`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -1275,7 +1226,7 @@ describe('testing pipe operator special', () => {
|
|||||||
describe('nests binary expressions correctly', () => {
|
describe('nests binary expressions correctly', () => {
|
||||||
it('works with the simple case', () => {
|
it('works with the simple case', () => {
|
||||||
const code = `const yo = 1 + 2`
|
const code = `const yo = 1 + 2`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body[0]).toEqual({
|
expect(body[0]).toEqual({
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
start: 0,
|
start: 0,
|
||||||
@ -1319,7 +1270,7 @@ describe('nests binary expressions correctly', () => {
|
|||||||
it('should nest according to precedence with multiply first', () => {
|
it('should nest according to precedence with multiply first', () => {
|
||||||
// should be binExp { binExp { lit-1 * lit-2 } + lit}
|
// should be binExp { binExp { lit-1 * lit-2 } + lit}
|
||||||
const code = `const yo = 1 * 2 + 3`
|
const code = `const yo = 1 * 2 + 3`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body[0]).toEqual({
|
expect(body[0]).toEqual({
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
start: 0,
|
start: 0,
|
||||||
@ -1376,7 +1327,7 @@ describe('nests binary expressions correctly', () => {
|
|||||||
it('should nest according to precedence with sum first', () => {
|
it('should nest according to precedence with sum first', () => {
|
||||||
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
|
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
|
||||||
const code = `const yo = 1 + 2 * 3`
|
const code = `const yo = 1 + 2 * 3`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect(body[0]).toEqual({
|
expect(body[0]).toEqual({
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
start: 0,
|
start: 0,
|
||||||
@ -1432,7 +1383,7 @@ describe('nests binary expressions correctly', () => {
|
|||||||
})
|
})
|
||||||
it('should nest properly with two opperators of equal precedence', () => {
|
it('should nest properly with two opperators of equal precedence', () => {
|
||||||
const code = `const yo = 1 + 2 - 3`
|
const code = `const yo = 1 + 2 - 3`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect((body[0] as any).declarations[0].init).toEqual({
|
expect((body[0] as any).declarations[0].init).toEqual({
|
||||||
type: 'BinaryExpression',
|
type: 'BinaryExpression',
|
||||||
start: 11,
|
start: 11,
|
||||||
@ -1469,7 +1420,7 @@ describe('nests binary expressions correctly', () => {
|
|||||||
})
|
})
|
||||||
it('should nest properly with two opperators of equal (but higher) precedence', () => {
|
it('should nest properly with two opperators of equal (but higher) precedence', () => {
|
||||||
const code = `const yo = 1 * 2 / 3`
|
const code = `const yo = 1 * 2 / 3`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
expect((body[0] as any).declarations[0].init).toEqual({
|
expect((body[0] as any).declarations[0].init).toEqual({
|
||||||
type: 'BinaryExpression',
|
type: 'BinaryExpression',
|
||||||
start: 11,
|
start: 11,
|
||||||
@ -1506,7 +1457,7 @@ describe('nests binary expressions correctly', () => {
|
|||||||
})
|
})
|
||||||
it('should nest properly with longer example', () => {
|
it('should nest properly with longer example', () => {
|
||||||
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
|
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
const init = (body[0] as any).declarations[0].init
|
const init = (body[0] as any).declarations[0].init
|
||||||
expect(init).toEqual({
|
expect(init).toEqual({
|
||||||
type: 'BinaryExpression',
|
type: 'BinaryExpression',
|
||||||
@ -1560,7 +1511,7 @@ const yo = { a: { b: { c: '123' } } }
|
|||||||
// this is a comment
|
// this is a comment
|
||||||
const key = 'c'`
|
const key = 'c'`
|
||||||
const nonCodeMetaInstance = {
|
const nonCodeMetaInstance = {
|
||||||
type: 'NoneCodeNode',
|
type: 'NonCodeNode',
|
||||||
start: code.indexOf('\n// this is a comment'),
|
start: code.indexOf('\n// this is a comment'),
|
||||||
end: code.indexOf('const key'),
|
end: code.indexOf('const key'),
|
||||||
value: {
|
value: {
|
||||||
@ -1568,18 +1519,16 @@ const key = 'c'`
|
|||||||
value: 'this is a comment',
|
value: 'this is a comment',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const { nonCodeMeta } = parser_wasm(code)
|
const { nonCodeMeta } = parse(code)
|
||||||
expect(nonCodeMeta.noneCodeNodes[0]).toEqual(nonCodeMetaInstance)
|
expect(nonCodeMeta.nonCodeNodes[0]).toEqual(nonCodeMetaInstance)
|
||||||
|
|
||||||
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
|
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
|
||||||
const codeWithExtraStartWhitespace = '\n\n\n' + code
|
const codeWithExtraStartWhitespace = '\n\n\n' + code
|
||||||
const { nonCodeMeta: nonCodeMeta2 } = parser_wasm(
|
const { nonCodeMeta: nonCodeMeta2 } = parse(codeWithExtraStartWhitespace)
|
||||||
codeWithExtraStartWhitespace
|
expect(nonCodeMeta2.nonCodeNodes[0].value).toStrictEqual(
|
||||||
)
|
|
||||||
expect(nonCodeMeta2.noneCodeNodes[0].value).toStrictEqual(
|
|
||||||
nonCodeMetaInstance.value
|
nonCodeMetaInstance.value
|
||||||
)
|
)
|
||||||
expect(nonCodeMeta2.noneCodeNodes[0].start).not.toBe(
|
expect(nonCodeMeta2.nonCodeNodes[0].start).not.toBe(
|
||||||
nonCodeMetaInstance.start
|
nonCodeMetaInstance.start
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -1593,12 +1542,12 @@ const key = 'c'`
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
|
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
const indexOfSecondLineToExpression = 2
|
const indexOfSecondLineToExpression = 2
|
||||||
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
||||||
.noneCodeNodes
|
.nonCodeNodes
|
||||||
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
|
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
|
||||||
type: 'NoneCodeNode',
|
type: 'NonCodeNode',
|
||||||
start: 106,
|
start: 106,
|
||||||
end: 166,
|
end: 166,
|
||||||
value: {
|
value: {
|
||||||
@ -1617,11 +1566,11 @@ const key = 'c'`
|
|||||||
' |> rx(90, %)',
|
' |> rx(90, %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
||||||
.noneCodeNodes
|
.nonCodeNodes
|
||||||
expect(sketchNonCodeMeta[3]).toEqual({
|
expect(sketchNonCodeMeta[3]).toEqual({
|
||||||
type: 'NoneCodeNode',
|
type: 'NonCodeNode',
|
||||||
start: 125,
|
start: 125,
|
||||||
end: 141,
|
end: 141,
|
||||||
value: {
|
value: {
|
||||||
@ -1635,7 +1584,7 @@ const key = 'c'`
|
|||||||
describe('test UnaryExpression', () => {
|
describe('test UnaryExpression', () => {
|
||||||
it('should parse a unary expression in simple var dec situation', () => {
|
it('should parse a unary expression in simple var dec situation', () => {
|
||||||
const code = `const myVar = -min(4, 100)`
|
const code = `const myVar = -min(4, 100)`
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
||||||
expect(myVarInit).toEqual({
|
expect(myVarInit).toEqual({
|
||||||
type: 'UnaryExpression',
|
type: 'UnaryExpression',
|
||||||
@ -1661,7 +1610,7 @@ describe('test UnaryExpression', () => {
|
|||||||
describe('testing nested call expressions', () => {
|
describe('testing nested call expressions', () => {
|
||||||
it('callExp in a binExp in a callExp', () => {
|
it('callExp in a binExp in a callExp', () => {
|
||||||
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
|
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
||||||
expect(myVarInit).toEqual({
|
expect(myVarInit).toEqual({
|
||||||
type: 'CallExpression',
|
type: 'CallExpression',
|
||||||
@ -1699,7 +1648,7 @@ describe('testing nested call expressions', () => {
|
|||||||
describe('should recognise callExpresions in binaryExpressions', () => {
|
describe('should recognise callExpresions in binaryExpressions', () => {
|
||||||
const code = "xLineTo(segEndX('seg02', %) + 1, %)"
|
const code = "xLineTo(segEndX('seg02', %) + 1, %)"
|
||||||
it('should recognise the callExp', () => {
|
it('should recognise the callExp', () => {
|
||||||
const { body } = parser_wasm(code)
|
const { body } = parse(code)
|
||||||
const callExpArgs = (body?.[0] as any).expression?.arguments
|
const callExpArgs = (body?.[0] as any).expression?.arguments
|
||||||
expect(callExpArgs).toEqual([
|
expect(callExpArgs).toEqual([
|
||||||
{
|
{
|
||||||
@ -1738,12 +1687,17 @@ describe('parsing errors', () => {
|
|||||||
|
|
||||||
let _theError
|
let _theError
|
||||||
try {
|
try {
|
||||||
const result = expect(parser_wasm(code))
|
const result = expect(parse(code))
|
||||||
console.log('result', result)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_theError = e
|
_theError = e
|
||||||
}
|
}
|
||||||
const theError = _theError as any
|
const theError = _theError as any
|
||||||
expect(theError).toEqual(new KCLUnexpectedError('Brace', [[29, 30]]))
|
expect(theError).toEqual(
|
||||||
|
new KCLError(
|
||||||
|
'unexpected',
|
||||||
|
'Unexpected token Token { token_type: Brace, start: 29, end: 30, value: "}" }',
|
||||||
|
[[29, 30]]
|
||||||
|
)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import { Program } from './abstractSyntaxTreeTypes'
|
|
||||||
import { parse_js } from '../wasm-lib/pkg/wasm_lib'
|
|
||||||
import { initPromise } from './rust'
|
|
||||||
import { Token } from './tokeniser'
|
|
||||||
import { KCLError } from './errors'
|
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
|
||||||
|
|
||||||
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
|
|
||||||
ranges.map(([start, end]) => [start, end])
|
|
||||||
|
|
||||||
export const parser_wasm = (code: string): Program => {
|
|
||||||
try {
|
|
||||||
const program: Program = parse_js(code)
|
|
||||||
return program
|
|
||||||
} catch (e: any) {
|
|
||||||
const parsed: RustKclError = JSON.parse(e.toString())
|
|
||||||
const kclError = new KCLError(
|
|
||||||
parsed.kind,
|
|
||||||
parsed.msg,
|
|
||||||
rangeTypeFix(parsed.sourceRanges)
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(kclError)
|
|
||||||
throw kclError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function asyncParser(code: string): Promise<Program> {
|
|
||||||
await initPromise
|
|
||||||
try {
|
|
||||||
const program: Program = parse_js(code)
|
|
||||||
return program
|
|
||||||
} catch (e: any) {
|
|
||||||
const parsed: RustKclError = JSON.parse(e.toString())
|
|
||||||
const kclError = new KCLError(
|
|
||||||
parsed.kind,
|
|
||||||
parsed.msg,
|
|
||||||
rangeTypeFix(parsed.sourceRanges)
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(kclError)
|
|
||||||
throw kclError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rangeOfToken(token: Token | undefined): [number, number][] {
|
|
||||||
return token === undefined ? [] : [[token.start, token.end]]
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
|
||||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
|
||||||
export type { ObjectExpression } from '../wasm-lib/kcl/bindings/ObjectExpression'
|
|
||||||
export type { MemberExpression } from '../wasm-lib/kcl/bindings/MemberExpression'
|
|
||||||
export type { PipeExpression } from '../wasm-lib/kcl/bindings/PipeExpression'
|
|
||||||
export type { VariableDeclaration } from '../wasm-lib/kcl/bindings/VariableDeclaration'
|
|
||||||
export type { PipeSubstitution } from '../wasm-lib/kcl/bindings/PipeSubstitution'
|
|
||||||
export type { Identifier } from '../wasm-lib/kcl/bindings/Identifier'
|
|
||||||
export type { UnaryExpression } from '../wasm-lib/kcl/bindings/UnaryExpression'
|
|
||||||
export type { BinaryExpression } from '../wasm-lib/kcl/bindings/BinaryExpression'
|
|
||||||
export type { ReturnStatement } from '../wasm-lib/kcl/bindings/ReturnStatement'
|
|
||||||
export type { ExpressionStatement } from '../wasm-lib/kcl/bindings/ExpressionStatement'
|
|
||||||
export type { CallExpression } from '../wasm-lib/kcl/bindings/CallExpression'
|
|
||||||
export type { VariableDeclarator } from '../wasm-lib/kcl/bindings/VariableDeclarator'
|
|
||||||
export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
|
|
||||||
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
|
||||||
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
|
||||||
|
|
||||||
export type SyntaxType =
|
|
||||||
| 'Program'
|
|
||||||
| 'ExpressionStatement'
|
|
||||||
| 'BinaryExpression'
|
|
||||||
| 'CallExpression'
|
|
||||||
| 'Identifier'
|
|
||||||
| 'ReturnStatement'
|
|
||||||
| 'VariableDeclaration'
|
|
||||||
| 'VariableDeclarator'
|
|
||||||
| 'MemberExpression'
|
|
||||||
| 'ArrayExpression'
|
|
||||||
| 'ObjectExpression'
|
|
||||||
| 'ObjectProperty'
|
|
||||||
| 'FunctionExpression'
|
|
||||||
| 'PipeExpression'
|
|
||||||
| 'PipeSubstitution'
|
|
||||||
| 'Literal'
|
|
||||||
| 'NoneCodeNode'
|
|
||||||
| 'UnaryExpression'
|
|
@ -1,5 +1,4 @@
|
|||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { parse, initPromise } from './wasm'
|
||||||
import { initPromise } from './rust'
|
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
@ -8,12 +7,13 @@ describe('testing artifacts', () => {
|
|||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
test('sketch artifacts', async () => {
|
test('sketch artifacts', async () => {
|
||||||
const code = `
|
const code = `
|
||||||
const mySketch001 = startSketchAt([0, 0])
|
const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
show(mySketch001)`
|
show(mySketch001)`
|
||||||
const programMemory = await enginelessExecutor(parser_wasm(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const shown = programMemory?.return?.map(
|
const shown = programMemory?.return?.map(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -28,7 +28,7 @@ show(mySketch001)`
|
|||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
sourceRange: [21, 42],
|
sourceRange: [46, 71],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value: [
|
value: [
|
||||||
@ -38,7 +38,7 @@ show(mySketch001)`
|
|||||||
to: [-1.59, -1.54],
|
to: [-1.59, -1.54],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [48, 73],
|
sourceRange: [77, 102],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -48,7 +48,7 @@ show(mySketch001)`
|
|||||||
from: [-1.59, -1.54],
|
from: [-1.59, -1.54],
|
||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [79, 103],
|
sourceRange: [108, 132],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -56,20 +56,22 @@ show(mySketch001)`
|
|||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
__meta: [{ sourceRange: [21, 42] }],
|
planeId: expect.any(String),
|
||||||
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('extrude artifacts', async () => {
|
test('extrude artifacts', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const code = `
|
const code = `
|
||||||
const mySketch001 = startSketchAt([0, 0])
|
const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> extrude(2, %)
|
|> extrude(2, %)
|
||||||
show(mySketch001)`
|
show(mySketch001)`
|
||||||
const programMemory = await enginelessExecutor(parser_wasm(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const shown = programMemory?.return?.map(
|
const shown = programMemory?.return?.map(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -83,7 +85,7 @@ show(mySketch001)`
|
|||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
__meta: [{ sourceRange: [21, 42] }],
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
@ -91,7 +93,8 @@ show(mySketch001)`
|
|||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
// TODO #153 in order for getExtrudeWallTransform to work we need to query the engine for the location of a face.
|
// TODO #153 in order for getExtrudeWallTransform to work we need to query the engine for the location of a face.
|
||||||
const code = `
|
const code = `
|
||||||
const sk1 = startSketchAt([0, 0])
|
const sk1 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-2.5, 0], %)
|
|> lineTo([-2.5, 0], %)
|
||||||
|> lineTo({ to: [0, 10], tag: "p" }, %)
|
|> lineTo({ to: [0, 10], tag: "p" }, %)
|
||||||
|> lineTo([2.5, 0], %)
|
|> lineTo([2.5, 0], %)
|
||||||
@ -100,7 +103,8 @@ const sk1 = startSketchAt([0, 0])
|
|||||||
// |> ry(5, %)
|
// |> ry(5, %)
|
||||||
const theExtrude = extrude(2, sk1)
|
const theExtrude = extrude(2, sk1)
|
||||||
// const theTransf = getExtrudeWallTransform('p', theExtrude)
|
// const theTransf = getExtrudeWallTransform('p', theExtrude)
|
||||||
const sk2 = startSketchAt([0, 0])
|
const sk2 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-2.5, 0], %)
|
|> lineTo([-2.5, 0], %)
|
||||||
|> lineTo({ to: [0, 3], tag: "p" }, %)
|
|> lineTo({ to: [0, 3], tag: "p" }, %)
|
||||||
|> lineTo([2.5, 0], %)
|
|> lineTo([2.5, 0], %)
|
||||||
@ -109,7 +113,7 @@ const sk2 = startSketchAt([0, 0])
|
|||||||
|
|
||||||
|
|
||||||
show(theExtrude, sk2)`
|
show(theExtrude, sk2)`
|
||||||
const programMemory = await enginelessExecutor(parser_wasm(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const geos = programMemory?.return?.map(
|
const geos = programMemory?.return?.map(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -123,7 +127,7 @@ show(theExtrude, sk2)`
|
|||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
__meta: [{ sourceRange: [13, 34] }],
|
__meta: [{ sourceRange: [38, 63] }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'ExtrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
@ -132,7 +136,7 @@ show(theExtrude, sk2)`
|
|||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
__meta: [{ sourceRange: [302, 323] }],
|
__meta: [{ sourceRange: [356, 381] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
|
|
||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { parse, ProgramMemory, SketchGroup, initPromise } from './wasm'
|
||||||
import { ProgramMemory, SketchGroup } from './executor'
|
|
||||||
import { initPromise } from './rust'
|
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { vi } from 'vitest'
|
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
@ -44,7 +41,8 @@ const newVar = myVar + 1`
|
|||||||
expect(root.magicNum.value).toBe(69)
|
expect(root.magicNum.value).toBe(69)
|
||||||
})
|
})
|
||||||
it('sketch declaration', async () => {
|
it('sketch declaration', async () => {
|
||||||
let code = `const mySketch = startSketchAt([0,0])
|
let code = `const mySketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> lineTo({to: [0,2], tag: "myPath"}, %)
|
|> lineTo({to: [0,2], tag: "myPath"}, %)
|
||||||
|> lineTo([2,3], %)
|
|> lineTo([2,3], %)
|
||||||
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
|
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
|
||||||
@ -60,7 +58,7 @@ show(mySketch)
|
|||||||
to: [0, 2],
|
to: [0, 2],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [43, 80],
|
sourceRange: [72, 109],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
name: 'myPath',
|
name: 'myPath',
|
||||||
@ -71,7 +69,7 @@ show(mySketch)
|
|||||||
from: [0, 2],
|
from: [0, 2],
|
||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [86, 102],
|
sourceRange: [115, 131],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -80,7 +78,7 @@ show(mySketch)
|
|||||||
to: [5, -1],
|
to: [5, -1],
|
||||||
from: [2, 3],
|
from: [2, 3],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [108, 151],
|
sourceRange: [137, 180],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
name: 'rightPath',
|
name: 'rightPath',
|
||||||
@ -90,8 +88,8 @@ show(mySketch)
|
|||||||
expect(_return).toEqual([
|
expect(_return).toEqual([
|
||||||
{
|
{
|
||||||
type: 'Identifier',
|
type: 'Identifier',
|
||||||
start: 174,
|
start: 203,
|
||||||
end: 182,
|
end: 211,
|
||||||
name: 'mySketch',
|
name: 'mySketch',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@ -135,7 +133,8 @@ show(mySketch)
|
|||||||
it('execute pipe sketch into call expression', async () => {
|
it('execute pipe sketch into call expression', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const code = [
|
const code = [
|
||||||
'const mySk1 = startSketchAt([0,0])',
|
"const mySk1 = startSketchOn('XY')",
|
||||||
|
' |> startProfileAt([0,0], %)',
|
||||||
' |> lineTo([1,1], %)',
|
' |> lineTo([1,1], %)',
|
||||||
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
|
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
|
||||||
' |> lineTo([1,1], %)',
|
' |> lineTo([1,1], %)',
|
||||||
@ -150,7 +149,7 @@ show(mySketch)
|
|||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
sourceRange: [14, 34],
|
sourceRange: [39, 63],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value: [
|
value: [
|
||||||
@ -160,7 +159,7 @@ show(mySketch)
|
|||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [40, 56],
|
sourceRange: [69, 85],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -169,7 +168,7 @@ show(mySketch)
|
|||||||
to: [0, 1],
|
to: [0, 1],
|
||||||
from: [1, 1],
|
from: [1, 1],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [62, 100],
|
sourceRange: [91, 129],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
name: 'myPath',
|
name: 'myPath',
|
||||||
@ -180,7 +179,7 @@ show(mySketch)
|
|||||||
from: [0, 1],
|
from: [0, 1],
|
||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [106, 122],
|
sourceRange: [135, 151],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -188,7 +187,8 @@ show(mySketch)
|
|||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
__meta: [{ sourceRange: [14, 34] }],
|
planeId: expect.any(String),
|
||||||
|
__meta: [{ sourceRange: [39, 63] }],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('execute array expression', async () => {
|
it('execute array expression', async () => {
|
||||||
@ -332,7 +332,8 @@ describe('testing math operators', () => {
|
|||||||
})
|
})
|
||||||
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
|
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
|
||||||
const code = [
|
const code = [
|
||||||
'const part001 = startSketchAt([0, 0])',
|
"const part001 = startSketchOn('XY')",
|
||||||
|
' |> startProfileAt([0, 0], %)',
|
||||||
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
@ -344,7 +345,8 @@ describe('testing math operators', () => {
|
|||||||
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
||||||
const code = [
|
const code = [
|
||||||
`const myVar = 3`,
|
`const myVar = 3`,
|
||||||
`const part001 = startSketchAt([0, 0])`,
|
`const part001 = startSketchOn('XY')`,
|
||||||
|
` |> startProfileAt([0, 0], %)`,
|
||||||
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
|
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
|
||||||
` |> line([`,
|
` |> line([`,
|
||||||
` min(segLen('seg01', %), myVar),`,
|
` min(segLen('seg01', %), myVar),`,
|
||||||
@ -380,7 +382,8 @@ describe('testing math operators', () => {
|
|||||||
describe('Testing Errors', () => {
|
describe('Testing Errors', () => {
|
||||||
it('should throw an error when a variable is not defined', async () => {
|
it('should throw an error when a variable is not defined', async () => {
|
||||||
const code = `const myVar = 5
|
const code = `const myVar = 5
|
||||||
const theExtrude = startSketchAt([0, 0])
|
const theExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([-2.4, 5], %)
|
|> line([-2.4, 5], %)
|
||||||
|> line([-0.76], myVarZ, %)
|
|> line([-0.76], myVarZ, %)
|
||||||
|> line([5,5], %)
|
|> line([5,5], %)
|
||||||
@ -391,7 +394,7 @@ show(theExtrude)`
|
|||||||
new KCLError(
|
new KCLError(
|
||||||
'undefined_value',
|
'undefined_value',
|
||||||
'memory item key `myVarZ` is not defined',
|
'memory item key `myVarZ` is not defined',
|
||||||
[[100, 106]]
|
[[129, 135]]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -403,7 +406,7 @@ async function exe(
|
|||||||
code: string,
|
code: string,
|
||||||
programMemory: ProgramMemory = { root: {}, return: null }
|
programMemory: ProgramMemory = { root: {}, return: null }
|
||||||
) {
|
) {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
|
|
||||||
const result = await enginelessExecutor(ast, programMemory)
|
const result = await enginelessExecutor(ast, programMemory)
|
||||||
return result
|
return result
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
import { Program } from './abstractSyntaxTreeTypes'
|
|
||||||
import {
|
|
||||||
EngineCommandManager,
|
|
||||||
ArtifactMap,
|
|
||||||
SourceRangeMap,
|
|
||||||
} from './std/engineConnection'
|
|
||||||
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
|
|
||||||
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
|
||||||
import { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
|
|
||||||
import { KCLError } from './errors'
|
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
|
||||||
import { rangeTypeFix } from './abstractSyntaxTree'
|
|
||||||
|
|
||||||
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
|
|
||||||
export type { Position } from '../wasm-lib/kcl/bindings/Position'
|
|
||||||
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
|
|
||||||
export type { Path } from '../wasm-lib/kcl/bindings/Path'
|
|
||||||
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
|
|
||||||
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
|
||||||
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
|
||||||
|
|
||||||
export type PathToNode = [string | number, string][]
|
|
||||||
|
|
||||||
interface Memory {
|
|
||||||
[key: string]: MemoryItem
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProgramMemory {
|
|
||||||
root: Memory
|
|
||||||
return: ProgramReturn | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const executor = async (
|
|
||||||
node: Program,
|
|
||||||
programMemory: ProgramMemory = { root: {}, return: null },
|
|
||||||
engineCommandManager: EngineCommandManager,
|
|
||||||
// work around while the gemotry is still be stored on the frontend
|
|
||||||
// will be removed when the stream UI is added.
|
|
||||||
tempMapCallback: (a: {
|
|
||||||
artifactMap: ArtifactMap
|
|
||||||
sourceRangeMap: SourceRangeMap
|
|
||||||
}) => void = () => {}
|
|
||||||
): Promise<ProgramMemory> => {
|
|
||||||
engineCommandManager.startNewSession()
|
|
||||||
const _programMemory = await _executor(
|
|
||||||
node,
|
|
||||||
programMemory,
|
|
||||||
engineCommandManager
|
|
||||||
)
|
|
||||||
const { artifactMap, sourceRangeMap } =
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
|
||||||
tempMapCallback({ artifactMap, sourceRangeMap })
|
|
||||||
|
|
||||||
engineCommandManager.endSession()
|
|
||||||
return _programMemory
|
|
||||||
}
|
|
||||||
|
|
||||||
export const _executor = async (
|
|
||||||
node: Program,
|
|
||||||
programMemory: ProgramMemory = { root: {}, return: null },
|
|
||||||
engineCommandManager: EngineCommandManager
|
|
||||||
): Promise<ProgramMemory> => {
|
|
||||||
try {
|
|
||||||
const memory: ProgramMemory = await execute_wasm(
|
|
||||||
JSON.stringify(node),
|
|
||||||
JSON.stringify(programMemory),
|
|
||||||
engineCommandManager
|
|
||||||
)
|
|
||||||
return memory
|
|
||||||
} catch (e: any) {
|
|
||||||
const parsed: RustKclError = JSON.parse(e.toString())
|
|
||||||
const kclError = new KCLError(
|
|
||||||
parsed.kind,
|
|
||||||
parsed.msg,
|
|
||||||
rangeTypeFix(parsed.sourceRanges)
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(kclError)
|
|
||||||
throw kclError
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { Identifier, parse, initPromise } from './wasm'
|
||||||
import { initPromise } from './rust'
|
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
@ -20,11 +19,88 @@ const sk3 = startSketchAt([0, 0])
|
|||||||
lineToSubstringIndex + subStr.length,
|
lineToSubstringIndex + subStr.length,
|
||||||
]
|
]
|
||||||
|
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const { node } = getNodeFromPath<any>(ast, nodePath)
|
const { node } = getNodeFromPath<any>(ast, nodePath)
|
||||||
|
|
||||||
expect([node.start, node.end]).toEqual(sourceRange)
|
expect([node.start, node.end]).toEqual(sourceRange)
|
||||||
expect(node.type).toBe('CallExpression')
|
expect(node.type).toBe('CallExpression')
|
||||||
})
|
})
|
||||||
|
it('gets path right for function definition params', () => {
|
||||||
|
const code = `fn cube = (pos, scale) => {
|
||||||
|
const sg = startSketchAt(pos)
|
||||||
|
|> line([0, scale], %)
|
||||||
|
|> line([scale, 0], %)
|
||||||
|
|> line([0, -scale], %)
|
||||||
|
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const b1 = cube([0,0], 10)`
|
||||||
|
const subStr = 'pos, scale'
|
||||||
|
const subStrIndex = code.indexOf(subStr)
|
||||||
|
const sourceRange: [number, number] = [
|
||||||
|
subStrIndex,
|
||||||
|
subStrIndex + 'pos'.length,
|
||||||
|
]
|
||||||
|
|
||||||
|
const ast = parse(code)
|
||||||
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
|
const node = getNodeFromPath<Identifier>(ast, nodePath).node
|
||||||
|
|
||||||
|
expect(nodePath).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[0, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', ''],
|
||||||
|
['params', 'FunctionExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
])
|
||||||
|
expect(node.type).toBe('Identifier')
|
||||||
|
expect(node.name).toBe('pos')
|
||||||
|
})
|
||||||
|
it('gets path right for deep within function definition body', () => {
|
||||||
|
const code = `fn cube = (pos, scale) => {
|
||||||
|
const sg = startSketchAt(pos)
|
||||||
|
|> line([0, scale], %)
|
||||||
|
|> line([scale, 0], %)
|
||||||
|
|> line([0, -scale], %)
|
||||||
|
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const b1 = cube([0,0], 10)`
|
||||||
|
const subStr = 'scale, 0'
|
||||||
|
const subStrIndex = code.indexOf(subStr)
|
||||||
|
const sourceRange: [number, number] = [
|
||||||
|
subStrIndex,
|
||||||
|
subStrIndex + 'scale'.length,
|
||||||
|
]
|
||||||
|
|
||||||
|
const ast = parse(code)
|
||||||
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
|
const node = getNodeFromPath<Identifier>(ast, nodePath).node
|
||||||
|
expect(nodePath).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[0, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', ''],
|
||||||
|
['body', 'FunctionExpression'],
|
||||||
|
['body', 'FunctionExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', ''],
|
||||||
|
['body', 'PipeExpression'],
|
||||||
|
[2, 'index'],
|
||||||
|
['arguments', 'CallExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
['elements', 'ArrayExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
])
|
||||||
|
expect(node.type).toBe('Identifier')
|
||||||
|
expect(node.name).toBe('scale')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { parse, recast, initPromise } from './wasm'
|
||||||
import {
|
import {
|
||||||
createLiteral,
|
createLiteral,
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
@ -13,8 +13,6 @@ import {
|
|||||||
giveSketchFnCallTag,
|
giveSketchFnCallTag,
|
||||||
moveValueIntoNewVariable,
|
moveValueIntoNewVariable,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { recast } from './recast'
|
|
||||||
import { initPromise } from './rust'
|
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
@ -106,13 +104,13 @@ describe('Testing addSketchTo', () => {
|
|||||||
body: [],
|
body: [],
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
nonCodeMeta: { noneCodeNodes: {}, start: null },
|
nonCodeMeta: { nonCodeNodes: {}, start: null },
|
||||||
},
|
},
|
||||||
'yz'
|
'yz'
|
||||||
)
|
)
|
||||||
const str = recast(result.modifiedAst)
|
const str = recast(result.modifiedAst)
|
||||||
expect(str).toBe(`const part001 = startSketchAt('default')
|
expect(str).toBe(`const part001 = startSketchOn('YZ')
|
||||||
|> ry(90, %)
|
|> startProfileAt('default', %)
|
||||||
|> line('default', %)
|
|> line('default', %)
|
||||||
show(part001)
|
show(part001)
|
||||||
`)
|
`)
|
||||||
@ -126,7 +124,7 @@ function giveSketchFnCallTagTestHelper(
|
|||||||
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
|
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
|
||||||
// this wrapper changes the input and output to code
|
// this wrapper changes the input and output to code
|
||||||
// making it more of an integration test, but easier to read the test intention is the goal
|
// making it more of an integration test, but easier to read the test intention is the goal
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const start = code.indexOf(searchStr)
|
const start = code.indexOf(searchStr)
|
||||||
const range: [number, number] = [start, start + searchStr.length]
|
const range: [number, number] = [start, start + searchStr.length]
|
||||||
const { modifiedAst, tag, isTagExisting } = giveSketchFnCallTag(ast, range)
|
const { modifiedAst, tag, isTagExisting } = giveSketchFnCallTag(ast, range)
|
||||||
@ -135,7 +133,8 @@ function giveSketchFnCallTagTestHelper(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('Testing giveSketchFnCallTag', () => {
|
describe('Testing giveSketchFnCallTag', () => {
|
||||||
const code = `const part001 = startSketchAt([0, 0])
|
const code = `const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([-2.57, -0.13], %)
|
|> line([-2.57, -0.13], %)
|
||||||
|> line([0, 0.83], %)
|
|> line([0, 0.83], %)
|
||||||
|> line([0.82, 0.34], %)
|
|> line([0.82, 0.34], %)
|
||||||
@ -176,7 +175,7 @@ show(part001)`
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Testing moveValueIntoNewVariable', () => {
|
describe('Testing moveValueIntoNewVariable', () => {
|
||||||
const fn = (fnName: string) => `const ${fnName} = (x) => {
|
const fn = (fnName: string) => `fn ${fnName} = (x) => {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -187,7 +186,8 @@ fn ghi = (x) => {
|
|||||||
const abc = 3
|
const abc = 3
|
||||||
const identifierGuy = 5
|
const identifierGuy = 5
|
||||||
const yo = 5 + 6
|
const yo = 5 + 6
|
||||||
const part001 = startSketchAt([-1.2, 4.83])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-1.2, 4.83], %)
|
||||||
|> line([2.8, 0], %)
|
|> line([2.8, 0], %)
|
||||||
|> angledLine([100 + 100, 3.09], %)
|
|> angledLine([100 + 100, 3.09], %)
|
||||||
|> angledLine([abc, 3.09], %)
|
|> angledLine([abc, 3.09], %)
|
||||||
@ -197,7 +197,7 @@ const part001 = startSketchAt([-1.2, 4.83])
|
|||||||
const yo2 = hmm([identifierGuy + 5])
|
const yo2 = hmm([identifierGuy + 5])
|
||||||
show(part001)`
|
show(part001)`
|
||||||
it('should move a binary expression into a new variable', async () => {
|
it('should move a binary expression into a new variable', async () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const startIndex = code.indexOf('100 + 100') + 1
|
const startIndex = code.indexOf('100 + 100') + 1
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
@ -211,7 +211,7 @@ show(part001)`
|
|||||||
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
||||||
})
|
})
|
||||||
it('should move a value into a new variable', async () => {
|
it('should move a value into a new variable', async () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const startIndex = code.indexOf('2.8') + 1
|
const startIndex = code.indexOf('2.8') + 1
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
@ -225,7 +225,7 @@ show(part001)`
|
|||||||
expect(newCode).toContain(`line([newVar, 0], %)`)
|
expect(newCode).toContain(`line([newVar, 0], %)`)
|
||||||
})
|
})
|
||||||
it('should move a callExpression into a new variable', async () => {
|
it('should move a callExpression into a new variable', async () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const startIndex = code.indexOf('def(')
|
const startIndex = code.indexOf('def(')
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
@ -239,7 +239,7 @@ show(part001)`
|
|||||||
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
||||||
})
|
})
|
||||||
it('should move a binary expression with call expression into a new variable', async () => {
|
it('should move a binary expression with call expression into a new variable', async () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const startIndex = code.indexOf('jkl(') + 1
|
const startIndex = code.indexOf('jkl(') + 1
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
@ -253,7 +253,7 @@ show(part001)`
|
|||||||
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
|
||||||
})
|
})
|
||||||
it('should move a identifier into a new variable', async () => {
|
it('should move a identifier into a new variable', async () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const startIndex = code.indexOf('identifierGuy +') + 1
|
const startIndex = code.indexOf('identifierGuy +') + 1
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Selection, TooTip } from '../useStore'
|
import { Selection, ToolTip } from '../useStore'
|
||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
@ -14,48 +14,56 @@ import {
|
|||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
UnaryExpression,
|
UnaryExpression,
|
||||||
BinaryExpression,
|
BinaryExpression,
|
||||||
} from './abstractSyntaxTreeTypes'
|
PathToNode,
|
||||||
|
ProgramMemory,
|
||||||
|
} from './wasm'
|
||||||
import {
|
import {
|
||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
} from './queryAst'
|
} from './queryAst'
|
||||||
import { PathToNode, ProgramMemory } from './executor'
|
|
||||||
import {
|
import {
|
||||||
addTagForSketchOnFace,
|
addTagForSketchOnFace,
|
||||||
getFirstArg,
|
getFirstArg,
|
||||||
createFirstArg,
|
createFirstArg,
|
||||||
} from './std/sketch'
|
} from './std/sketch'
|
||||||
|
import { isLiteralArrayOrStatic } from './std/sketchcombos'
|
||||||
|
|
||||||
export function addStartSketch(
|
export function addStartSketch(
|
||||||
node: Program,
|
node: Program,
|
||||||
|
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz',
|
||||||
start: [number, number],
|
start: [number, number],
|
||||||
end: [number, number]
|
end: [number, number]
|
||||||
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
|
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const _name = findUniqueName(node, 'part')
|
const _name = findUniqueName(node, 'part')
|
||||||
|
|
||||||
const startSketchAt = createCallExpression('startSketchAt', [
|
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
|
||||||
|
createLiteral(axis.toUpperCase()),
|
||||||
|
])
|
||||||
|
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
||||||
createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
|
createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
|
||||||
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
const initialLineTo = createCallExpression('line', [
|
const initialLineTo = createCallExpression('line', [
|
||||||
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
|
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
|
||||||
const pipeBody = [startSketchAt, initialLineTo]
|
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
|
||||||
|
|
||||||
const variableDeclaration = createVariableDeclaration(
|
const variableDeclaration = createVariableDeclaration(
|
||||||
_name,
|
_name,
|
||||||
createPipeExpression(pipeBody)
|
createPipeExpression(pipeBody)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const newIndex = node.body.length
|
||||||
_node.body = [...node.body, variableDeclaration]
|
_node.body = [...node.body, variableDeclaration]
|
||||||
|
|
||||||
let pathToNode: PathToNode = [
|
let pathToNode: PathToNode = [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
['0', 'index'],
|
[newIndex.toString(10), 'index'],
|
||||||
['declarations', 'VariableDeclaration'],
|
['declarations', 'VariableDeclaration'],
|
||||||
['0', 'index'],
|
['0', 'index'],
|
||||||
['init', 'VariableDeclarator'],
|
['init', 'VariableDeclarator'],
|
||||||
@ -76,11 +84,11 @@ export function addSketchTo(
|
|||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const _name = name || findUniqueName(node, 'part')
|
const _name = name || findUniqueName(node, 'part')
|
||||||
|
|
||||||
const startSketchAt = createCallExpressionStdLib('startSketchAt', [
|
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
|
||||||
createLiteral('default'),
|
createLiteral(axis.toUpperCase()),
|
||||||
])
|
])
|
||||||
const rotate = createCallExpression(axis === 'xz' ? 'rx' : 'ry', [
|
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
||||||
createLiteral(90),
|
createLiteral('default'),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
const initialLineTo = createCallExpressionStdLib('line', [
|
const initialLineTo = createCallExpressionStdLib('line', [
|
||||||
@ -88,10 +96,7 @@ export function addSketchTo(
|
|||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
|
||||||
const pipeBody =
|
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
|
||||||
axis !== 'xy'
|
|
||||||
? [startSketchAt, rotate, initialLineTo]
|
|
||||||
: [startSketchAt, initialLineTo]
|
|
||||||
|
|
||||||
const variableDeclaration = createVariableDeclaration(
|
const variableDeclaration = createVariableDeclaration(
|
||||||
_name,
|
_name,
|
||||||
@ -191,7 +196,7 @@ export function mutateArrExp(
|
|||||||
): boolean {
|
): boolean {
|
||||||
if (node.type === 'ArrayExpression') {
|
if (node.type === 'ArrayExpression') {
|
||||||
node.elements.forEach((element, i) => {
|
node.elements.forEach((element, i) => {
|
||||||
if (element.type === 'Literal') {
|
if (isLiteralArrayOrStatic(element)) {
|
||||||
node.elements[i] = updateWith.elements[i]
|
node.elements[i] = updateWith.elements[i]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -209,8 +214,8 @@ export function mutateObjExpProp(
|
|||||||
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
|
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
|
||||||
if (keyIndex !== -1) {
|
if (keyIndex !== -1) {
|
||||||
if (
|
if (
|
||||||
updateWith.type === 'Literal' &&
|
isLiteralArrayOrStatic(updateWith) &&
|
||||||
node.properties[keyIndex].value.type === 'Literal'
|
isLiteralArrayOrStatic(node.properties[keyIndex].value)
|
||||||
) {
|
) {
|
||||||
node.properties[keyIndex].value = updateWith
|
node.properties[keyIndex].value = updateWith
|
||||||
return true
|
return true
|
||||||
@ -220,7 +225,7 @@ export function mutateObjExpProp(
|
|||||||
) {
|
) {
|
||||||
const arrExp = node.properties[keyIndex].value as ArrayExpression
|
const arrExp = node.properties[keyIndex].value as ArrayExpression
|
||||||
arrExp.elements.forEach((element, i) => {
|
arrExp.elements.forEach((element, i) => {
|
||||||
if (element.type === 'Literal') {
|
if (isLiteralArrayOrStatic(element)) {
|
||||||
arrExp.elements[i] = updateWith.elements[i]
|
arrExp.elements[i] = updateWith.elements[i]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -303,7 +308,11 @@ export function extrudeSketch(
|
|||||||
}
|
}
|
||||||
const name = findUniqueName(node, 'part')
|
const name = findUniqueName(node, 'part')
|
||||||
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
||||||
const showCallIndex = getShowIndex(_node)
|
let showCallIndex = getShowIndex(_node)
|
||||||
|
if (showCallIndex == -1) {
|
||||||
|
// We didn't find a show, so let's just append everything
|
||||||
|
showCallIndex = _node.body.length
|
||||||
|
}
|
||||||
_node.body.splice(showCallIndex, 0, VariableDeclaration)
|
_node.body.splice(showCallIndex, 0, VariableDeclaration)
|
||||||
const pathToExtrudeArg: PathToNode = [
|
const pathToExtrudeArg: PathToNode = [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
@ -315,7 +324,7 @@ export function extrudeSketch(
|
|||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
modifiedAst: addToShow(_node, name),
|
modifiedAst: node,
|
||||||
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
|
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
|
||||||
pathToExtrudeArg,
|
pathToExtrudeArg,
|
||||||
}
|
}
|
||||||
@ -531,7 +540,7 @@ export function createPipeExpression(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
body,
|
body,
|
||||||
nonCodeMeta: { noneCodeNodes: {}, start: null },
|
nonCodeMeta: { nonCodeNodes: {}, start: null },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -633,7 +642,7 @@ export function giveSketchFnCallTag(
|
|||||||
createLiteral(tag || findUniqueName(ast, 'seg', 2))) as Literal
|
createLiteral(tag || findUniqueName(ast, 'seg', 2))) as Literal
|
||||||
const tagStr = String(tagValue.value)
|
const tagStr = String(tagValue.value)
|
||||||
const newFirstArg = createFirstArg(
|
const newFirstArg = createFirstArg(
|
||||||
primaryCallExp.callee.name as TooTip,
|
primaryCallExp.callee.name as ToolTip,
|
||||||
firstArg.val,
|
firstArg.val,
|
||||||
tagValue
|
tagValue
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { parser_wasm } from './abstractSyntaxTree'
|
import { parse, recast, initPromise } from './wasm'
|
||||||
import {
|
import {
|
||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
isTypeInValue,
|
isTypeInValue,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
} from './queryAst'
|
} from './queryAst'
|
||||||
import { initPromise } from './rust'
|
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import {
|
import {
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
@ -13,7 +12,6 @@ import {
|
|||||||
createLiteral,
|
createLiteral,
|
||||||
createPipeSubstitution,
|
createPipeSubstitution,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { recast } from './recast'
|
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
@ -28,7 +26,8 @@ const halfArmAngle = armAngle / 2
|
|||||||
const arrExpShouldNotBeIncluded = [1, 2, 3]
|
const arrExpShouldNotBeIncluded = [1, 2, 3]
|
||||||
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
|
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
|
||||||
|
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> yLineTo(1, %)
|
|> yLineTo(1, %)
|
||||||
|> xLine(3.84, %) // selection-range-7ish-before-this
|
|> xLine(3.84, %) // selection-range-7ish-before-this
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ const variableBelowShouldNotBeIncluded = 3
|
|||||||
|
|
||||||
show(part001)`
|
show(part001)`
|
||||||
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
|
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
|
||||||
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
||||||
@ -59,7 +58,8 @@ show(part001)`
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('testing argIsNotIdentifier', () => {
|
describe('testing argIsNotIdentifier', () => {
|
||||||
const code = `const part001 = startSketchAt([-1.2, 4.83])
|
const code = `const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-1.2, 4.83], %)
|
||||||
|> line([2.8, 0], %)
|
|> line([2.8, 0], %)
|
||||||
|> angledLine([100 + 100, 3.09], %)
|
|> angledLine([100 + 100, 3.09], %)
|
||||||
|> angledLine([abc, 3.09], %)
|
|> angledLine([abc, 3.09], %)
|
||||||
@ -70,7 +70,7 @@ const yo = 5 + 6
|
|||||||
const yo2 = hmm([identifierGuy + 5])
|
const yo2 = hmm([identifierGuy + 5])
|
||||||
show(part001)`
|
show(part001)`
|
||||||
it('find a safe binaryExpression', () => {
|
it('find a safe binaryExpression', () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const rangeStart = code.indexOf('100 + 100') + 2
|
const rangeStart = code.indexOf('100 + 100') + 2
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
@ -84,7 +84,7 @@ show(part001)`
|
|||||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||||
})
|
})
|
||||||
it('find a safe Identifier', () => {
|
it('find a safe Identifier', () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const rangeStart = code.indexOf('abc')
|
const rangeStart = code.indexOf('abc')
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
@ -92,7 +92,7 @@ show(part001)`
|
|||||||
expect(code.slice(result.value.start, result.value.end)).toBe('abc')
|
expect(code.slice(result.value.start, result.value.end)).toBe('abc')
|
||||||
})
|
})
|
||||||
it('find a safe CallExpression', () => {
|
it('find a safe CallExpression', () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const rangeStart = code.indexOf('def')
|
const rangeStart = code.indexOf('def')
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
@ -106,7 +106,7 @@ show(part001)`
|
|||||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||||
})
|
})
|
||||||
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
|
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const rangeStart = code.indexOf('ghi')
|
const rangeStart = code.indexOf('ghi')
|
||||||
const range: [number, number] = [rangeStart, rangeStart]
|
const range: [number, number] = [rangeStart, rangeStart]
|
||||||
const result = isNodeSafeToReplace(ast, range)
|
const result = isNodeSafeToReplace(ast, range)
|
||||||
@ -115,7 +115,7 @@ show(part001)`
|
|||||||
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
|
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
|
||||||
})
|
})
|
||||||
it('find an UNsafe Identifier, as it is a callee', () => {
|
it('find an UNsafe Identifier, as it is a callee', () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const rangeStart = code.indexOf('ine([2.8,')
|
const rangeStart = code.indexOf('ine([2.8,')
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||||
expect(result.isSafe).toBe(false)
|
expect(result.isSafe).toBe(false)
|
||||||
@ -125,7 +125,7 @@ show(part001)`
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
it("find a safe BinaryExpression that's assigned to a variable", () => {
|
it("find a safe BinaryExpression that's assigned to a variable", () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const rangeStart = code.indexOf('5 + 6') + 1
|
const rangeStart = code.indexOf('5 + 6') + 1
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
@ -139,7 +139,7 @@ show(part001)`
|
|||||||
expect(outCode).toContain(`const yo = replaceName`)
|
expect(outCode).toContain(`const yo = replaceName`)
|
||||||
})
|
})
|
||||||
it('find a safe BinaryExpression that has a CallExpression within', () => {
|
it('find a safe BinaryExpression that has a CallExpression within', () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const rangeStart = code.indexOf('jkl') + 1
|
const rangeStart = code.indexOf('jkl') + 1
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
@ -155,7 +155,7 @@ show(part001)`
|
|||||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||||
})
|
})
|
||||||
it('find a safe BinaryExpression within a CallExpression', () => {
|
it('find a safe BinaryExpression within a CallExpression', () => {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const rangeStart = code.indexOf('identifierGuy') + 1
|
const rangeStart = code.indexOf('identifierGuy') + 1
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
@ -196,14 +196,15 @@ show(part001)`
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('testing getNodePathFromSourceRange', () => {
|
describe('testing getNodePathFromSourceRange', () => {
|
||||||
const code = `const part001 = startSketchAt([0.39, -0.05])
|
const code = `const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.39, -0.05], %)
|
||||||
|> line([0.94, 2.61], %)
|
|> line([0.94, 2.61], %)
|
||||||
|> line([-0.21, -1.4], %)
|
|> line([-0.21, -1.4], %)
|
||||||
show(part001)`
|
show(part001)`
|
||||||
it('finds the second line when cursor is put at the end', () => {
|
it('finds the second line when cursor is put at the end', () => {
|
||||||
const searchLn = `line([0.94, 2.61], %)`
|
const searchLn = `line([0.94, 2.61], %)`
|
||||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
['body', ''],
|
['body', ''],
|
||||||
@ -212,13 +213,13 @@ show(part001)`
|
|||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
['init', ''],
|
['init', ''],
|
||||||
['body', 'PipeExpression'],
|
['body', 'PipeExpression'],
|
||||||
[1, 'index'],
|
[2, 'index'],
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
it('finds the last line when cursor is put at the end', () => {
|
it('finds the last line when cursor is put at the end', () => {
|
||||||
const searchLn = `line([-0.21, -1.4], %)`
|
const searchLn = `line([-0.21, -1.4], %)`
|
||||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||||
const expected = [
|
const expected = [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
@ -227,7 +228,7 @@ show(part001)`
|
|||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
['init', ''],
|
['init', ''],
|
||||||
['body', 'PipeExpression'],
|
['body', 'PipeExpression'],
|
||||||
[2, 'index'],
|
[3, 'index'],
|
||||||
]
|
]
|
||||||
expect(result).toEqual(expected)
|
expect(result).toEqual(expected)
|
||||||
// expect similar result for start of line
|
// expect similar result for start of line
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { PathToNode, ProgramMemory, SketchGroup, SourceRange } from './executor'
|
import { Selection, ToolTip } from '../useStore'
|
||||||
import { Selection, TooTip } from '../useStore'
|
|
||||||
import {
|
import {
|
||||||
BinaryExpression,
|
BinaryExpression,
|
||||||
Program,
|
Program,
|
||||||
@ -10,7 +9,11 @@ import {
|
|||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
ReturnStatement,
|
ReturnStatement,
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
} from './abstractSyntaxTreeTypes'
|
PathToNode,
|
||||||
|
ProgramMemory,
|
||||||
|
SketchGroup,
|
||||||
|
SourceRange,
|
||||||
|
} from './wasm'
|
||||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||||
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
||||||
import { getAngle } from '../lib/utils'
|
import { getAngle } from '../lib/utils'
|
||||||
@ -239,7 +242,29 @@ function moreNodePathFromSourceRange(
|
|||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
console.error('not implemented')
|
if (_node.type === 'FunctionExpression' && isInRange) {
|
||||||
|
for (let i = 0; i < _node.params.length; i++) {
|
||||||
|
const param = _node.params[i]
|
||||||
|
if (param.start <= start && param.end >= end) {
|
||||||
|
path.push(['params', 'FunctionExpression'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(param, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_node.body.start <= start && _node.body.end >= end) {
|
||||||
|
path.push(['body', 'FunctionExpression'])
|
||||||
|
const fnBody = _node.body.body
|
||||||
|
for (let i = 0; i < fnBody.length; i++) {
|
||||||
|
const statement = fnBody[i]
|
||||||
|
if (statement.start <= start && statement.end >= end) {
|
||||||
|
path.push(['body', 'FunctionExpression'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.error('not implemented: ' + node.type)
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,7 +482,7 @@ export function isLinesParallelAndConstrained(
|
|||||||
const secondaryFirstArg = getFirstArg(secondaryNode)
|
const secondaryFirstArg = getFirstArg(secondaryNode)
|
||||||
const constraintType = getConstraintType(
|
const constraintType = getConstraintType(
|
||||||
secondaryFirstArg.val,
|
secondaryFirstArg.val,
|
||||||
secondaryNode.callee.name as TooTip
|
secondaryNode.callee.name as ToolTip
|
||||||
)
|
)
|
||||||
const constraintLevel = getConstraintLevelFromSourceRange(
|
const constraintLevel = getConstraintLevelFromSourceRange(
|
||||||
secondaryLine.range,
|
secondaryLine.range,
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import { recast } from './recast'
|
import { parse, Program, recast, initPromise } from './wasm'
|
||||||
import { parser_wasm } from './abstractSyntaxTree'
|
|
||||||
import { Program } from './abstractSyntaxTreeTypes'
|
|
||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import { initPromise } from './rust'
|
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
@ -224,7 +221,7 @@ const key = 'c'
|
|||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('comments in a fn block', () => {
|
it('comments in a fn block', () => {
|
||||||
const code = `const myFn = () => {
|
const code = `fn myFn = () => {
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } }
|
||||||
|
|
||||||
@ -366,6 +363,6 @@ describe('it recasts binary expression using brackets where needed', () => {
|
|||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
function code2ast(code: string): { ast: Program } {
|
function code2ast(code: string): { ast: Program } {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
return { ast }
|
return { ast }
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { Program } from './abstractSyntaxTreeTypes'
|
|
||||||
import { recast_wasm } from '../wasm-lib/pkg/wasm_lib'
|
|
||||||
|
|
||||||
export const recast = (ast: Program): string => {
|
|
||||||
try {
|
|
||||||
const s: string = recast_wasm(JSON.stringify(ast))
|
|
||||||
return s
|
|
||||||
} catch (e) {
|
|
||||||
// TODO: do something real with the error.
|
|
||||||
console.log('recast', e)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
import init from '../wasm-lib/pkg/wasm_lib'
|
|
||||||
|
|
||||||
const initialise = async () => {
|
|
||||||
const baseUrl =
|
|
||||||
typeof window === 'undefined'
|
|
||||||
? 'http://127.0.0.1:3000'
|
|
||||||
: window.location.origin.includes('tauri://localhost')
|
|
||||||
? 'tauri://localhost'
|
|
||||||
: window.location.origin.includes('localhost')
|
|
||||||
? 'http://localhost:3000'
|
|
||||||
: window.location.origin && window.location.origin !== 'null'
|
|
||||||
? window.location.origin
|
|
||||||
: 'http://localhost:3000'
|
|
||||||
const fullUrl = baseUrl + '/wasm_lib_bg.wasm'
|
|
||||||
const input = await fetch(fullUrl)
|
|
||||||
const buffer = await input.arrayBuffer()
|
|
||||||
return init(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const initPromise = initialise()
|
|
@ -1,10 +1,16 @@
|
|||||||
import { SourceRange } from 'lang/executor'
|
import {
|
||||||
|
ProgramMemory,
|
||||||
|
SourceRange,
|
||||||
|
Program,
|
||||||
|
VariableDeclarator,
|
||||||
|
} from 'lang/wasm'
|
||||||
import { Selections } from 'useStore'
|
import { Selections } from 'useStore'
|
||||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
|
||||||
let lastMessage = ''
|
let lastMessage = ''
|
||||||
|
|
||||||
@ -13,9 +19,17 @@ interface CommandInfo {
|
|||||||
range: SourceRange
|
range: SourceRange
|
||||||
parentId?: string
|
parentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||||
|
|
||||||
interface ResultCommand extends CommandInfo {
|
interface ResultCommand extends CommandInfo {
|
||||||
type: 'result'
|
type: 'result'
|
||||||
data: any
|
data: any
|
||||||
|
raw: WebSocketResponse
|
||||||
|
}
|
||||||
|
interface FailedCommand extends CommandInfo {
|
||||||
|
type: 'failed'
|
||||||
|
errors: Models['FailureWebSocketResponse_type']['errors']
|
||||||
}
|
}
|
||||||
interface PendingCommand extends CommandInfo {
|
interface PendingCommand extends CommandInfo {
|
||||||
type: 'pending'
|
type: 'pending'
|
||||||
@ -24,7 +38,7 @@ interface PendingCommand extends CommandInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ArtifactMap {
|
export interface ArtifactMap {
|
||||||
[key: string]: ResultCommand | PendingCommand
|
[key: string]: ResultCommand | PendingCommand | FailedCommand
|
||||||
}
|
}
|
||||||
export interface SourceRangeMap {
|
export interface SourceRangeMap {
|
||||||
[key: string]: SourceRange
|
[key: string]: SourceRange
|
||||||
@ -35,7 +49,10 @@ interface NewTrackArgs {
|
|||||||
mediaStream: MediaStream
|
mediaStream: MediaStream
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
// This looks funny, I know. This is needed because node and the browser
|
||||||
|
// disagree as to the type. In a browser it's a number, but in node it's a
|
||||||
|
// "Timeout".
|
||||||
|
type Timeout = ReturnType<typeof setTimeout>
|
||||||
|
|
||||||
type ClientMetrics = Models['ClientMetrics_type']
|
type ClientMetrics = Models['ClientMetrics_type']
|
||||||
|
|
||||||
@ -48,6 +65,9 @@ export class EngineConnection {
|
|||||||
unreliableDataChannel?: RTCDataChannel
|
unreliableDataChannel?: RTCDataChannel
|
||||||
|
|
||||||
private ready: boolean
|
private ready: boolean
|
||||||
|
private connecting: boolean
|
||||||
|
private dead: boolean
|
||||||
|
private failedConnTimeout: Timeout | null
|
||||||
|
|
||||||
readonly url: string
|
readonly url: string
|
||||||
private readonly token?: string
|
private readonly token?: string
|
||||||
@ -83,6 +103,9 @@ export class EngineConnection {
|
|||||||
this.url = url
|
this.url = url
|
||||||
this.token = token
|
this.token = token
|
||||||
this.ready = false
|
this.ready = false
|
||||||
|
this.connecting = false
|
||||||
|
this.dead = false
|
||||||
|
this.failedConnTimeout = null
|
||||||
this.onWebsocketOpen = onWebsocketOpen
|
this.onWebsocketOpen = onWebsocketOpen
|
||||||
this.onDataChannelOpen = onDataChannelOpen
|
this.onDataChannelOpen = onDataChannelOpen
|
||||||
this.onEngineConnectionOpen = onEngineConnectionOpen
|
this.onEngineConnectionOpen = onEngineConnectionOpen
|
||||||
@ -93,7 +116,10 @@ export class EngineConnection {
|
|||||||
// TODO(paultag): This ought to be tweakable.
|
// TODO(paultag): This ought to be tweakable.
|
||||||
const pingIntervalMs = 10000
|
const pingIntervalMs = 10000
|
||||||
|
|
||||||
setInterval(() => {
|
let pingInterval = setInterval(() => {
|
||||||
|
if (this.dead) {
|
||||||
|
clearInterval(pingInterval)
|
||||||
|
}
|
||||||
if (this.isReady()) {
|
if (this.isReady()) {
|
||||||
// When we're online, every 10 seconds, we'll attempt to put a 'ping'
|
// When we're online, every 10 seconds, we'll attempt to put a 'ping'
|
||||||
// command through the WebSocket connection. This will help both ends
|
// command through the WebSocket connection. This will help both ends
|
||||||
@ -102,6 +128,24 @@ export class EngineConnection {
|
|||||||
this.send({ type: 'ping' })
|
this.send({ type: 'ping' })
|
||||||
}
|
}
|
||||||
}, pingIntervalMs)
|
}, pingIntervalMs)
|
||||||
|
|
||||||
|
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
|
||||||
|
let connectInterval = setInterval(() => {
|
||||||
|
if (this.dead) {
|
||||||
|
clearInterval(connectInterval)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.isReady()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('connecting via retry')
|
||||||
|
this.connect()
|
||||||
|
}, connectionTimeoutMs)
|
||||||
|
}
|
||||||
|
// isConnecting will return true when connect has been called, but the full
|
||||||
|
// WebRTC is not online.
|
||||||
|
isConnecting() {
|
||||||
|
return this.connecting
|
||||||
}
|
}
|
||||||
// isReady will return true only when the WebRTC *and* WebSocket connection
|
// isReady will return true only when the WebRTC *and* WebSocket connection
|
||||||
// are connected. During setup, the WebSocket connection comes online first,
|
// are connected. During setup, the WebSocket connection comes online first,
|
||||||
@ -110,6 +154,10 @@ export class EngineConnection {
|
|||||||
isReady() {
|
isReady() {
|
||||||
return this.ready
|
return this.ready
|
||||||
}
|
}
|
||||||
|
tearDown() {
|
||||||
|
this.dead = true
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
// shouldTrace will return true when Sentry should be used to instrument
|
// shouldTrace will return true when Sentry should be used to instrument
|
||||||
// the Engine.
|
// the Engine.
|
||||||
shouldTrace() {
|
shouldTrace() {
|
||||||
@ -121,8 +169,10 @@ export class EngineConnection {
|
|||||||
// This will attempt the full handshake, and retry if the connection
|
// This will attempt the full handshake, and retry if the connection
|
||||||
// did not establish.
|
// did not establish.
|
||||||
connect() {
|
connect() {
|
||||||
// TODO(paultag): make this safe to call multiple times, and figure out
|
console.log('connect was called')
|
||||||
// when a connection is in progress (state: connecting or something).
|
if (this.isConnecting() || this.isReady()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Information on the connect transaction
|
// Information on the connect transaction
|
||||||
|
|
||||||
@ -310,6 +360,11 @@ export class EngineConnection {
|
|||||||
if (this.shouldTrace()) {
|
if (this.shouldTrace()) {
|
||||||
iceSpan.resolve?.()
|
iceSpan.resolve?.()
|
||||||
}
|
}
|
||||||
|
} else if (this.pc?.iceConnectionState === 'failed') {
|
||||||
|
// failed is a terminal state; let's explicitly kill the
|
||||||
|
// connection to the server at this point.
|
||||||
|
console.log('failed to negotiate ice connection; restarting')
|
||||||
|
this.close()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -354,20 +409,6 @@ export class EngineConnection {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(paultag): This ought to be both controllable, as well as something
|
|
||||||
// like exponential backoff to have some grace on the backend, as well as
|
|
||||||
// fix responsiveness for clients that had a weird network hiccup.
|
|
||||||
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.isReady()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log('engine connection timeout on connection, retrying')
|
|
||||||
this.close()
|
|
||||||
this.connect()
|
|
||||||
}, connectionTimeoutMs)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.pc.addEventListener('track', (event) => {
|
this.pc.addEventListener('track', (event) => {
|
||||||
@ -393,8 +434,6 @@ export class EngineConnection {
|
|||||||
|
|
||||||
let videoTrack = mediaStream.getVideoTracks()[0]
|
let videoTrack = mediaStream.getVideoTracks()[0]
|
||||||
this.pc?.getStats(videoTrack).then((videoTrackStats) => {
|
this.pc?.getStats(videoTrack).then((videoTrackStats) => {
|
||||||
// TODO(paultag): this needs type information from the KittyCAD typescript
|
|
||||||
// library once it's updated
|
|
||||||
let client_metrics: ClientMetrics = {
|
let client_metrics: ClientMetrics = {
|
||||||
rtc_frames_decoded: 0,
|
rtc_frames_decoded: 0,
|
||||||
rtc_frames_dropped: 0,
|
rtc_frames_dropped: 0,
|
||||||
@ -422,12 +461,13 @@ export class EngineConnection {
|
|||||||
videoTrackReport.framesReceived
|
videoTrackReport.framesReceived
|
||||||
client_metrics.rtc_frames_per_second =
|
client_metrics.rtc_frames_per_second =
|
||||||
videoTrackReport.framesPerSecond || 0
|
videoTrackReport.framesPerSecond || 0
|
||||||
client_metrics.rtc_freeze_count = videoTrackReport.freezeCount
|
client_metrics.rtc_freeze_count =
|
||||||
|
videoTrackReport.freezeCount || 0
|
||||||
client_metrics.rtc_jitter_sec = videoTrackReport.jitter
|
client_metrics.rtc_jitter_sec = videoTrackReport.jitter
|
||||||
client_metrics.rtc_keyframes_decoded =
|
client_metrics.rtc_keyframes_decoded =
|
||||||
videoTrackReport.keyFramesDecoded
|
videoTrackReport.keyFramesDecoded
|
||||||
client_metrics.rtc_total_freezes_duration_sec =
|
client_metrics.rtc_total_freezes_duration_sec =
|
||||||
videoTrackReport.totalFreezesDuration
|
videoTrackReport.totalFreezesDuration || 0
|
||||||
} else if (videoTrackReport.type === 'transport') {
|
} else if (videoTrackReport.type === 'transport') {
|
||||||
// videoTrackReport.bytesReceived,
|
// videoTrackReport.bytesReceived,
|
||||||
// videoTrackReport.bytesSent,
|
// videoTrackReport.bytesSent,
|
||||||
@ -456,8 +496,11 @@ export class EngineConnection {
|
|||||||
|
|
||||||
this.onDataChannelOpen(this)
|
this.onDataChannelOpen(this)
|
||||||
|
|
||||||
this.onEngineConnectionOpen(this)
|
|
||||||
this.ready = true
|
this.ready = true
|
||||||
|
this.connecting = false
|
||||||
|
// Do this after we set the connection is ready to avoid errors when
|
||||||
|
// we try to send messages before the connection is ready.
|
||||||
|
this.onEngineConnectionOpen(this)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.unreliableDataChannel.addEventListener('close', (event) => {
|
this.unreliableDataChannel.addEventListener('close', (event) => {
|
||||||
@ -471,8 +514,31 @@ export class EngineConnection {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
|
||||||
|
|
||||||
|
if (this.failedConnTimeout) {
|
||||||
|
console.log('clearing timeout before set')
|
||||||
|
clearTimeout(this.failedConnTimeout)
|
||||||
|
this.failedConnTimeout = null
|
||||||
|
}
|
||||||
|
console.log('timeout set')
|
||||||
|
this.failedConnTimeout = setTimeout(() => {
|
||||||
|
if (this.isReady()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('engine connection timeout on connection, closing')
|
||||||
|
this.close()
|
||||||
|
}, connectionTimeoutMs)
|
||||||
|
|
||||||
this.onConnectionStarted(this)
|
this.onConnectionStarted(this)
|
||||||
}
|
}
|
||||||
|
unreliableSend(message: object | string) {
|
||||||
|
// TODO(paultag): Add in logic to determine the connection state and
|
||||||
|
// take actions if needed?
|
||||||
|
this.unreliableDataChannel?.send(
|
||||||
|
typeof message === 'string' ? message : JSON.stringify(message)
|
||||||
|
)
|
||||||
|
}
|
||||||
send(message: object | string) {
|
send(message: object | string) {
|
||||||
// TODO(paultag): Add in logic to determine the connection state and
|
// TODO(paultag): Add in logic to determine the connection state and
|
||||||
// take actions if needed?
|
// take actions if needed?
|
||||||
@ -488,9 +554,15 @@ export class EngineConnection {
|
|||||||
this.pc = undefined
|
this.pc = undefined
|
||||||
this.unreliableDataChannel = undefined
|
this.unreliableDataChannel = undefined
|
||||||
this.webrtcStatsCollector = undefined
|
this.webrtcStatsCollector = undefined
|
||||||
|
if (this.failedConnTimeout) {
|
||||||
|
console.log('closed timeout in close')
|
||||||
|
clearTimeout(this.failedConnTimeout)
|
||||||
|
this.failedConnTimeout = null
|
||||||
|
}
|
||||||
|
|
||||||
this.onClose(this)
|
this.onClose(this)
|
||||||
this.ready = false
|
this.ready = false
|
||||||
|
this.connecting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -521,6 +593,9 @@ export class EngineCommandManager {
|
|||||||
outSequence = 1
|
outSequence = 1
|
||||||
inSequence = 1
|
inSequence = 1
|
||||||
engineConnection?: EngineConnection
|
engineConnection?: EngineConnection
|
||||||
|
// Folks should realize that wait for ready does not get called _everytime_
|
||||||
|
// the connection resets and restarts, it only gets called the first time.
|
||||||
|
// Be careful what you put here.
|
||||||
waitForReady: Promise<void> = new Promise(() => {})
|
waitForReady: Promise<void> = new Promise(() => {})
|
||||||
private resolveReady = () => {}
|
private resolveReady = () => {}
|
||||||
|
|
||||||
@ -534,19 +609,36 @@ export class EngineCommandManager {
|
|||||||
[localUnsubscribeId: string]: (a: any) => void
|
[localUnsubscribeId: string]: (a: any) => void
|
||||||
}
|
}
|
||||||
} = {} as any
|
} = {} as any
|
||||||
constructor({
|
|
||||||
|
constructor() {
|
||||||
|
this.engineConnection = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
start({
|
||||||
setMediaStream,
|
setMediaStream,
|
||||||
setIsStreamReady,
|
setIsStreamReady,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
executeCode,
|
||||||
token,
|
token,
|
||||||
}: {
|
}: {
|
||||||
setMediaStream: (stream: MediaStream) => void
|
setMediaStream: (stream: MediaStream) => void
|
||||||
setIsStreamReady: (isStreamReady: boolean) => void
|
setIsStreamReady: (isStreamReady: boolean) => void
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
|
executeCode: (code?: string, force?: boolean) => void
|
||||||
token?: string
|
token?: string
|
||||||
}) {
|
}) {
|
||||||
|
if (width === 0 || height === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we already have an engine connection, just need to resize the stream.
|
||||||
|
if (this.engineConnection) {
|
||||||
|
this.handleResize({ streamWidth: width, streamHeight: height })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.waitForReady = new Promise((resolve) => {
|
this.waitForReady = new Promise((resolve) => {
|
||||||
this.resolveReady = resolve
|
this.resolveReady = resolve
|
||||||
})
|
})
|
||||||
@ -557,6 +649,32 @@ export class EngineCommandManager {
|
|||||||
onEngineConnectionOpen: () => {
|
onEngineConnectionOpen: () => {
|
||||||
this.resolveReady()
|
this.resolveReady()
|
||||||
setIsStreamReady(true)
|
setIsStreamReady(true)
|
||||||
|
|
||||||
|
// Make the axis gizmo.
|
||||||
|
// We do this after the connection opened to avoid a race condition.
|
||||||
|
// Connected opened is the last thing that happens when the stream
|
||||||
|
// is ready.
|
||||||
|
// We also do this here because we want to ensure we create the gizmo
|
||||||
|
// and execute the code everytime the stream is restarted.
|
||||||
|
const gizmoId = uuidv4()
|
||||||
|
this.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: gizmoId,
|
||||||
|
cmd: {
|
||||||
|
type: 'make_axes_gizmo',
|
||||||
|
clobber: false,
|
||||||
|
// If true, axes gizmo will be placed in the corner of the screen.
|
||||||
|
// If false, it will be placed at the origin of the scene.
|
||||||
|
gizmo_mode: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// We execute the code here to make sure if the stream was to
|
||||||
|
// restart in a session, we want to make sure to execute the code.
|
||||||
|
// We force it to re-execute the code because we want to make sure
|
||||||
|
// the code is executed everytime the stream is restarted.
|
||||||
|
// We pass undefined for the code so it reads from the current state.
|
||||||
|
executeCode(undefined, true)
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
setIsStreamReady(false)
|
setIsStreamReady(false)
|
||||||
@ -607,6 +725,12 @@ export class EngineCommandManager {
|
|||||||
message.request_id
|
message.request_id
|
||||||
) {
|
) {
|
||||||
this.handleModelingCommand(message.resp, message.request_id)
|
this.handleModelingCommand(message.resp, message.request_id)
|
||||||
|
} else if (
|
||||||
|
!message.success &&
|
||||||
|
message.request_id &&
|
||||||
|
this.artifactMap[message.request_id]
|
||||||
|
) {
|
||||||
|
this.handleFailedModelingCommand(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -626,6 +750,30 @@ export class EngineCommandManager {
|
|||||||
|
|
||||||
this.engineConnection?.connect()
|
this.engineConnection?.connect()
|
||||||
}
|
}
|
||||||
|
handleResize({
|
||||||
|
streamWidth,
|
||||||
|
streamHeight,
|
||||||
|
}: {
|
||||||
|
streamWidth: number
|
||||||
|
streamHeight: number
|
||||||
|
}) {
|
||||||
|
console.log('handleResize', streamWidth, streamHeight)
|
||||||
|
if (!this.engineConnection?.isReady()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeCmd: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'reconfigure_stream',
|
||||||
|
width: streamWidth,
|
||||||
|
height: streamHeight,
|
||||||
|
fps: 60,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.engineConnection?.send(resizeCmd)
|
||||||
|
}
|
||||||
handleModelingCommand(message: WebSocketResponse, id: string) {
|
handleModelingCommand(message: WebSocketResponse, id: string) {
|
||||||
if (message.type !== 'modeling') {
|
if (message.type !== 'modeling') {
|
||||||
return
|
return
|
||||||
@ -644,12 +792,14 @@ export class EngineCommandManager {
|
|||||||
commandType: command.commandType,
|
commandType: command.commandType,
|
||||||
parentId: command.parentId ? command.parentId : undefined,
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
|
raw: message,
|
||||||
}
|
}
|
||||||
resolve({
|
resolve({
|
||||||
id,
|
id,
|
||||||
commandType: command.commandType,
|
commandType: command.commandType,
|
||||||
range: command.range,
|
range: command.range,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
|
raw: message,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
@ -657,11 +807,44 @@ export class EngineCommandManager {
|
|||||||
commandType: command?.commandType,
|
commandType: command?.commandType,
|
||||||
range: command?.range,
|
range: command?.range,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
|
raw: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleFailedModelingCommand({
|
||||||
|
request_id,
|
||||||
|
errors,
|
||||||
|
}: Models['FailureWebSocketResponse_type']) {
|
||||||
|
const id = request_id
|
||||||
|
if (!id) return
|
||||||
|
const command = this.artifactMap[id]
|
||||||
|
if (command && command.type === 'pending') {
|
||||||
|
const resolve = command.resolve
|
||||||
|
this.artifactMap[id] = {
|
||||||
|
type: 'failed',
|
||||||
|
range: command.range,
|
||||||
|
commandType: command.commandType,
|
||||||
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
resolve({
|
||||||
|
id,
|
||||||
|
commandType: command.commandType,
|
||||||
|
range: command.range,
|
||||||
|
errors,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.artifactMap[id] = {
|
||||||
|
type: 'failed',
|
||||||
|
range: command.range,
|
||||||
|
commandType: command.commandType,
|
||||||
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
|
errors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tearDown() {
|
tearDown() {
|
||||||
this.engineConnection?.close()
|
this.engineConnection?.tearDown()
|
||||||
}
|
}
|
||||||
startNewSession() {
|
startNewSession() {
|
||||||
this.artifactMap = {}
|
this.artifactMap = {}
|
||||||
@ -756,6 +939,14 @@ export class EngineCommandManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
sendSceneCommand(command: EngineCommand): Promise<any> {
|
sendSceneCommand(command: EngineCommand): Promise<any> {
|
||||||
|
if (this.engineConnection === undefined) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.engineConnection?.isReady()) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
command.type === 'modeling_cmd_req' &&
|
command.type === 'modeling_cmd_req' &&
|
||||||
command.cmd.type !== lastMessage
|
command.cmd.type !== lastMessage
|
||||||
@ -763,10 +954,6 @@ export class EngineCommandManager {
|
|||||||
console.log('sending command', command.cmd.type)
|
console.log('sending command', command.cmd.type)
|
||||||
lastMessage = command.cmd.type
|
lastMessage = command.cmd.type
|
||||||
}
|
}
|
||||||
if (!this.engineConnection?.isReady()) {
|
|
||||||
console.log('socket not ready')
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
||||||
const cmd = command.cmd
|
const cmd = command.cmd
|
||||||
if (
|
if (
|
||||||
@ -776,9 +963,7 @@ export class EngineCommandManager {
|
|||||||
) {
|
) {
|
||||||
cmd.sequence = this.outSequence
|
cmd.sequence = this.outSequence
|
||||||
this.outSequence++
|
this.outSequence++
|
||||||
this.engineConnection?.unreliableDataChannel?.send(
|
this.engineConnection?.unreliableSend(command)
|
||||||
JSON.stringify(command)
|
|
||||||
)
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
} else if (
|
} else if (
|
||||||
cmd.type === 'highlight_set_entity' &&
|
cmd.type === 'highlight_set_entity' &&
|
||||||
@ -786,9 +971,7 @@ export class EngineCommandManager {
|
|||||||
) {
|
) {
|
||||||
cmd.sequence = this.outSequence
|
cmd.sequence = this.outSequence
|
||||||
this.outSequence++
|
this.outSequence++
|
||||||
this.engineConnection?.unreliableDataChannel?.send(
|
this.engineConnection?.unreliableSend(command)
|
||||||
JSON.stringify(command)
|
|
||||||
)
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
} else if (
|
} else if (
|
||||||
cmd.type === 'mouse_move' &&
|
cmd.type === 'mouse_move' &&
|
||||||
@ -796,9 +979,7 @@ export class EngineCommandManager {
|
|||||||
) {
|
) {
|
||||||
cmd.sequence = this.outSequence
|
cmd.sequence = this.outSequence
|
||||||
this.outSequence++
|
this.outSequence++
|
||||||
this.engineConnection?.unreliableDataChannel?.send(
|
this.engineConnection?.unreliableSend(command)
|
||||||
JSON.stringify(command)
|
|
||||||
)
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
||||||
@ -814,10 +995,12 @@ export class EngineCommandManager {
|
|||||||
range: SourceRange
|
range: SourceRange
|
||||||
command: EngineCommand | string
|
command: EngineCommand | string
|
||||||
}): Promise<any> {
|
}): Promise<any> {
|
||||||
|
if (this.engineConnection === undefined) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
this.sourceRangeMap[id] = range
|
this.sourceRangeMap[id] = range
|
||||||
|
|
||||||
if (!this.engineConnection?.isReady()) {
|
if (!this.engineConnection?.isReady()) {
|
||||||
console.log('socket not ready')
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
this.engineConnection?.send(command)
|
this.engineConnection?.send(command)
|
||||||
@ -860,6 +1043,9 @@ export class EngineCommandManager {
|
|||||||
rangeStr: string,
|
rangeStr: string,
|
||||||
commandStr: string
|
commandStr: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
if (this.engineConnection === undefined) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
if (id === undefined) {
|
if (id === undefined) {
|
||||||
throw new Error('id is undefined')
|
throw new Error('id is undefined')
|
||||||
}
|
}
|
||||||
@ -871,7 +1057,10 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
const range: SourceRange = JSON.parse(rangeStr)
|
const range: SourceRange = JSON.parse(rangeStr)
|
||||||
|
|
||||||
return this.sendModelingCommand({ id, range, command: commandStr })
|
// We only care about the modeling command response.
|
||||||
|
return this.sendModelingCommand({ id, range, command: commandStr }).then(
|
||||||
|
({ raw }) => JSON.stringify(raw)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
commandResult(id: string): Promise<any> {
|
commandResult(id: string): Promise<any> {
|
||||||
const command = this.artifactMap[id]
|
const command = this.artifactMap[id]
|
||||||
@ -880,10 +1069,15 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
if (command.type === 'result') {
|
if (command.type === 'result') {
|
||||||
return command.data
|
return command.data
|
||||||
|
} else if (command.type === 'failed') {
|
||||||
|
return Promise.resolve(command.errors)
|
||||||
}
|
}
|
||||||
return command.promise
|
return command.promise
|
||||||
}
|
}
|
||||||
async waitForAllCommands(): Promise<{
|
async waitForAllCommands(
|
||||||
|
ast?: Program,
|
||||||
|
programMemory?: ProgramMemory
|
||||||
|
): Promise<{
|
||||||
artifactMap: ArtifactMap
|
artifactMap: ArtifactMap
|
||||||
sourceRangeMap: SourceRangeMap
|
sourceRangeMap: SourceRangeMap
|
||||||
}> {
|
}> {
|
||||||
@ -892,9 +1086,97 @@ export class EngineCommandManager {
|
|||||||
) as PendingCommand[]
|
) as PendingCommand[]
|
||||||
const proms = pendingCommands.map(({ promise }) => promise)
|
const proms = pendingCommands.map(({ promise }) => promise)
|
||||||
await Promise.all(proms)
|
await Promise.all(proms)
|
||||||
|
if (ast && programMemory) {
|
||||||
|
await this.fixIdMappings(ast, programMemory)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
artifactMap: this.artifactMap,
|
artifactMap: this.artifactMap,
|
||||||
sourceRangeMap: this.sourceRangeMap,
|
sourceRangeMap: this.sourceRangeMap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private async fixIdMappings(ast: Program, programMemory: ProgramMemory) {
|
||||||
|
if (this.engineConnection === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/* This is a temporary solution since the cmd_ids that are sent through when
|
||||||
|
sending 'extend_path' ids are not used as the segment ids.
|
||||||
|
|
||||||
|
We have a way to back fill them with 'path_get_info', however this relies on one
|
||||||
|
the sketchGroup array and the segements array returned from the server to be in
|
||||||
|
the same length and order. plus it's super hacky, we first use the path_id to get
|
||||||
|
the source range of the pipe expression then use the name of the variable to get
|
||||||
|
the sketchGroup from programMemory.
|
||||||
|
|
||||||
|
I feel queezy about relying on all these steps to always line up.
|
||||||
|
We have also had to pollute this EngineCommandManager class with knowledge of both the ast and programMemory
|
||||||
|
We should get the cmd_ids to match with the segment ids and delete this method.
|
||||||
|
*/
|
||||||
|
const pathInfoProms = []
|
||||||
|
for (const [id, artifact] of Object.entries(this.artifactMap)) {
|
||||||
|
if (artifact.commandType === 'start_path') {
|
||||||
|
pathInfoProms.push(
|
||||||
|
this.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'path_get_info',
|
||||||
|
path_id: id,
|
||||||
|
},
|
||||||
|
}).then(({ data }) => ({
|
||||||
|
originalId: id,
|
||||||
|
segments: data?.data?.segments,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathInfos = await Promise.all(pathInfoProms)
|
||||||
|
pathInfos.forEach(({ originalId, segments }) => {
|
||||||
|
const originalArtifact = this.artifactMap[originalId]
|
||||||
|
if (!originalArtifact || originalArtifact.type === 'pending') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const pipeExpPath = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
originalArtifact.range
|
||||||
|
)
|
||||||
|
const pipeExp = getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
pipeExpPath,
|
||||||
|
'VariableDeclarator'
|
||||||
|
).node
|
||||||
|
if (pipeExp.type !== 'VariableDeclarator') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const variableName = pipeExp.id.name
|
||||||
|
const memoryItem = programMemory.root[variableName]
|
||||||
|
if (!memoryItem) {
|
||||||
|
return
|
||||||
|
} else if (memoryItem.type !== 'SketchGroup') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const relevantSegments = segments.filter(
|
||||||
|
({ command_id }: { command_id: string | null }) => command_id
|
||||||
|
)
|
||||||
|
if (memoryItem.value.length !== relevantSegments.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (let i = 0; i < relevantSegments.length; i++) {
|
||||||
|
const engineSegment = relevantSegments[i]
|
||||||
|
const memorySegment = memoryItem.value[i]
|
||||||
|
const oldId = memorySegment.__geoMeta.id
|
||||||
|
const artifact = this.artifactMap[oldId]
|
||||||
|
delete this.artifactMap[oldId]
|
||||||
|
delete this.sourceRangeMap[oldId]
|
||||||
|
if (artifact) {
|
||||||
|
this.artifactMap[engineSegment.command_id] = artifact
|
||||||
|
this.sourceRangeMap[engineSegment.command_id] = artifact.range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const engineCommandManager = new EngineCommandManager()
|
||||||
|
@ -4,14 +4,11 @@ import {
|
|||||||
addNewSketchLn,
|
addNewSketchLn,
|
||||||
getYComponent,
|
getYComponent,
|
||||||
getXComponent,
|
getXComponent,
|
||||||
|
addCloseToPipe,
|
||||||
} from './sketch'
|
} from './sketch'
|
||||||
import { parser_wasm } from '../abstractSyntaxTree'
|
import { parse, recast, initPromise } from '../wasm'
|
||||||
import { getNodePathFromSourceRange } from '../queryAst'
|
import { getNodePathFromSourceRange } from '../queryAst'
|
||||||
import { recast } from '../recast'
|
|
||||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||||
import { initPromise } from '../rust'
|
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
|
||||||
|
|
||||||
const eachQuad: [number, [number, number]][] = [
|
const eachQuad: [number, [number, number]][] = [
|
||||||
[-315, [1, 1]],
|
[-315, [1, 1]],
|
||||||
@ -28,6 +25,8 @@ const eachQuad: [number, [number, number]][] = [
|
|||||||
[675, [1, -1]],
|
[675, [1, -1]],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
describe('testing getYComponent', () => {
|
describe('testing getYComponent', () => {
|
||||||
it('should return the vertical component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => {
|
it('should return the vertical component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => {
|
||||||
const expected: [number, number][] = []
|
const expected: [number, number][] = []
|
||||||
@ -97,7 +96,8 @@ describe('testing changeSketchArguments', () => {
|
|||||||
const lineAfterChange = 'lineTo([2, 3], %)'
|
const lineAfterChange = 'lineTo([2, 3], %)'
|
||||||
test('changeSketchArguments', async () => {
|
test('changeSketchArguments', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const genCode = (line: string) => `const mySketch001 = startSketchAt([0, 0])
|
const genCode = (line: string) => `const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> ${line}
|
|> ${line}
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
@ -105,7 +105,7 @@ show(mySketch001)
|
|||||||
`
|
`
|
||||||
const code = genCode(lineToChange)
|
const code = genCode(lineToChange)
|
||||||
const expectedCode = genCode(lineAfterChange)
|
const expectedCode = genCode(lineAfterChange)
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(lineToChange)
|
const sourceStart = code.indexOf(lineToChange)
|
||||||
const { modifiedAst } = changeSketchArguments(
|
const { modifiedAst } = changeSketchArguments(
|
||||||
@ -116,6 +116,7 @@ show(mySketch001)
|
|||||||
{
|
{
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'sketchEdit',
|
||||||
|
pathId: '',
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
pathToNode: [
|
pathToNode: [
|
||||||
@ -137,16 +138,17 @@ describe('testing addNewSketchLn', () => {
|
|||||||
test('addNewSketchLn', async () => {
|
test('addNewSketchLn', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const code = `
|
const code = `
|
||||||
const mySketch001 = startSketchAt([0, 0])
|
const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
show(mySketch001)`
|
show(mySketch001)`
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(lineToChange)
|
const sourceStart = code.indexOf(lineToChange)
|
||||||
expect(sourceStart).toBe(66)
|
expect(sourceStart).toBe(95)
|
||||||
const { modifiedAst } = addNewSketchLn({
|
let { modifiedAst } = addNewSketchLn({
|
||||||
node: ast,
|
node: ast,
|
||||||
programMemory,
|
programMemory,
|
||||||
to: [2, 3],
|
to: [2, 3],
|
||||||
@ -160,12 +162,35 @@ show(mySketch001)`
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const expectedCode = `const mySketch001 = startSketchAt([0, 0])
|
let expectedCode = `const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
|> lineTo([2, 3], %)
|
|> lineTo([2, 3], %)
|
||||||
show(mySketch001)
|
show(mySketch001)
|
||||||
|
`
|
||||||
|
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||||
|
|
||||||
|
modifiedAst = addCloseToPipe({
|
||||||
|
node: ast,
|
||||||
|
programMemory,
|
||||||
|
pathToNode: [
|
||||||
|
['body', ''],
|
||||||
|
[0, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', 'VariableDeclarator'],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expectedCode = `const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
// |> rx(45, %)
|
||||||
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|
|> lineTo([0.46, -5.82], %)
|
||||||
|
|> close(%)
|
||||||
|
show(mySketch001)
|
||||||
`
|
`
|
||||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||||
})
|
})
|
||||||
@ -175,14 +200,15 @@ describe('testing addTagForSketchOnFace', () => {
|
|||||||
it('needs to be in it', async () => {
|
it('needs to be in it', async () => {
|
||||||
const originalLine = 'lineTo([-1.59, -1.54], %)'
|
const originalLine = 'lineTo([-1.59, -1.54], %)'
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const genCode = (line: string) => `const mySketch001 = startSketchAt([0, 0])
|
const genCode = (line: string) => `const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> ${line}
|
|> ${line}
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
show(mySketch001)
|
show(mySketch001)
|
||||||
`
|
`
|
||||||
const code = genCode(originalLine)
|
const code = genCode(originalLine)
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(originalLine)
|
const sourceStart = code.indexOf(originalLine)
|
||||||
const sourceRange: [number, number] = [
|
const sourceRange: [number, number] = [
|
||||||
|
@ -4,9 +4,6 @@ import {
|
|||||||
SketchGroup,
|
SketchGroup,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
MemoryItem,
|
|
||||||
} from '../executor'
|
|
||||||
import {
|
|
||||||
Program,
|
Program,
|
||||||
PipeExpression,
|
PipeExpression,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
@ -14,13 +11,14 @@ import {
|
|||||||
Value,
|
Value,
|
||||||
Literal,
|
Literal,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
} from '../abstractSyntaxTreeTypes'
|
} from '../wasm'
|
||||||
import {
|
import {
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodeFromPathCurry,
|
getNodeFromPathCurry,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
} from '../queryAst'
|
} from '../queryAst'
|
||||||
import { GuiModes, toolTips, TooTip } from '../../useStore'
|
import { isLiteralArrayOrStatic } from './sketchcombos'
|
||||||
|
import { GuiModes, toolTips, ToolTip } from '../../useStore'
|
||||||
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||||
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
||||||
|
|
||||||
@ -37,7 +35,6 @@ import {
|
|||||||
findUniqueName,
|
findUniqueName,
|
||||||
} from '../modifyAst'
|
} from '../modifyAst'
|
||||||
import { roundOff, getLength, getAngle } from '../../lib/utils'
|
import { roundOff, getLength, getAngle } from '../../lib/utils'
|
||||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
|
||||||
import { perpendicularDistance } from 'sketch-helpers'
|
import { perpendicularDistance } from 'sketch-helpers'
|
||||||
|
|
||||||
export type Coords2d = [number, number]
|
export type Coords2d = [number, number]
|
||||||
@ -56,7 +53,7 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createFirstArg(
|
export function createFirstArg(
|
||||||
sketchFn: TooTip,
|
sketchFn: ToolTip,
|
||||||
val: Value | [Value, Value] | [Value, Value, Value],
|
val: Value | [Value, Value] | [Value, Value, Value],
|
||||||
tag?: Value
|
tag?: Value
|
||||||
): Value {
|
): Value {
|
||||||
@ -294,7 +291,7 @@ export const xLineTo: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newX = createLiteral(roundOff(to[0], 2))
|
const newX = createLiteral(roundOff(to[0], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newX
|
callExpression.arguments[0] = newX
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newX, 'to')
|
mutateObjExpProp(callExpression.arguments?.[0], newX, 'to')
|
||||||
@ -342,7 +339,7 @@ export const yLineTo: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newY = createLiteral(roundOff(to[1], 2))
|
const newY = createLiteral(roundOff(to[1], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newY
|
callExpression.arguments[0] = newY
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newY, 'to')
|
mutateObjExpProp(callExpression.arguments?.[0], newY, 'to')
|
||||||
@ -392,7 +389,7 @@ export const xLine: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newX = createLiteral(roundOff(to[0] - from[0], 2))
|
const newX = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newX
|
callExpression.arguments[0] = newX
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newX, 'length')
|
mutateObjExpProp(callExpression.arguments?.[0], newX, 'length')
|
||||||
@ -436,7 +433,7 @@ export const yLine: SketchLineHelper = {
|
|||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
const newY = createLiteral(roundOff(to[1] - from[1], 2))
|
const newY = createLiteral(roundOff(to[1] - from[1], 2))
|
||||||
if (callExpression.arguments?.[0]?.type === 'Literal') {
|
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||||
callExpression.arguments[0] = newY
|
callExpression.arguments[0] = newY
|
||||||
} else {
|
} else {
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], newY, 'length')
|
mutateObjExpProp(callExpression.arguments?.[0], newY, 'length')
|
||||||
@ -942,17 +939,29 @@ interface CreateLineFnCallArgs {
|
|||||||
programMemory: ProgramMemory
|
programMemory: ProgramMemory
|
||||||
to: [number, number]
|
to: [number, number]
|
||||||
from: [number, number]
|
from: [number, number]
|
||||||
fnName: TooTip
|
fnName: ToolTip
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function compareVec2Epsilon(
|
||||||
|
vec1: [number, number],
|
||||||
|
vec2: [number, number]
|
||||||
|
) {
|
||||||
|
const compareEpsilon = 0.015625 // or 2^-6
|
||||||
|
const xDifference = Math.abs(vec1[0] - vec2[0])
|
||||||
|
const yDifference = Math.abs(vec1[0] - vec2[0])
|
||||||
|
return xDifference < compareEpsilon && yDifference < compareEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
export function addNewSketchLn({
|
export function addNewSketchLn({
|
||||||
node: _node,
|
node: _node,
|
||||||
programMemory: previousProgramMemory,
|
programMemory: previousProgramMemory,
|
||||||
to,
|
to,
|
||||||
fnName,
|
fnName,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
}: Omit<CreateLineFnCallArgs, 'from'>): { modifiedAst: Program } {
|
}: Omit<CreateLineFnCallArgs, 'from'>): {
|
||||||
|
modifiedAst: Program
|
||||||
|
} {
|
||||||
const node = JSON.parse(JSON.stringify(_node))
|
const node = JSON.parse(JSON.stringify(_node))
|
||||||
const { add, updateArgs } = sketchLineHelperMap?.[fnName] || {}
|
const { add, updateArgs } = sketchLineHelperMap?.[fnName] || {}
|
||||||
if (!add || !updateArgs) throw new Error('not a sketch line helper')
|
if (!add || !updateArgs) throw new Error('not a sketch line helper')
|
||||||
@ -970,7 +979,6 @@ export function addNewSketchLn({
|
|||||||
|
|
||||||
const last = sketch.value[sketch.value.length - 1] || sketch.start
|
const last = sketch.value[sketch.value.length - 1] || sketch.start
|
||||||
const from = last.to
|
const from = last.to
|
||||||
|
|
||||||
return add({
|
return add({
|
||||||
node,
|
node,
|
||||||
previousProgramMemory,
|
previousProgramMemory,
|
||||||
@ -981,6 +989,29 @@ export function addNewSketchLn({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addCloseToPipe({
|
||||||
|
node,
|
||||||
|
pathToNode,
|
||||||
|
}: {
|
||||||
|
node: Program
|
||||||
|
programMemory: ProgramMemory
|
||||||
|
pathToNode: PathToNode
|
||||||
|
}) {
|
||||||
|
const _node = { ...node }
|
||||||
|
const closeExpression = createCallExpression('close', [
|
||||||
|
createPipeSubstitution(),
|
||||||
|
])
|
||||||
|
const pipeExpression = getNodeFromPath<PipeExpression>(
|
||||||
|
_node,
|
||||||
|
pathToNode,
|
||||||
|
'PipeExpression'
|
||||||
|
).node
|
||||||
|
if (pipeExpression.type !== 'PipeExpression')
|
||||||
|
throw new Error('not a pipe expression')
|
||||||
|
pipeExpression.body = [...pipeExpression.body, closeExpression]
|
||||||
|
return _node
|
||||||
|
}
|
||||||
|
|
||||||
export function replaceSketchLine({
|
export function replaceSketchLine({
|
||||||
node,
|
node,
|
||||||
programMemory,
|
programMemory,
|
||||||
@ -994,7 +1025,7 @@ export function replaceSketchLine({
|
|||||||
node: Program
|
node: Program
|
||||||
programMemory: ProgramMemory
|
programMemory: ProgramMemory
|
||||||
sourceRange: SourceRange
|
sourceRange: SourceRange
|
||||||
fnName: TooTip
|
fnName: ToolTip
|
||||||
to: [number, number]
|
to: [number, number]
|
||||||
from: [number, number]
|
from: [number, number]
|
||||||
createCallback: TransformCallback
|
createCallback: TransformCallback
|
||||||
@ -1036,10 +1067,11 @@ export function addTagForSketchOnFace(
|
|||||||
|
|
||||||
function isAngleLiteral(lineArugement: Value): boolean {
|
function isAngleLiteral(lineArugement: Value): boolean {
|
||||||
return lineArugement?.type === 'ArrayExpression'
|
return lineArugement?.type === 'ArrayExpression'
|
||||||
? lineArugement.elements[0].type === 'Literal'
|
? isLiteralArrayOrStatic(lineArugement.elements[0])
|
||||||
: lineArugement?.type === 'ObjectExpression'
|
: lineArugement?.type === 'ObjectExpression'
|
||||||
? lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
|
? isLiteralArrayOrStatic(
|
||||||
.type === 'Literal'
|
lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
|
||||||
|
)
|
||||||
: false
|
: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1172,7 +1204,7 @@ function getFirstArgValuesForAngleFns(callExpression: CallExpression): {
|
|||||||
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
|
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
|
||||||
const angle = firstArg.properties.find((p) => p.key.name === 'angle')?.value
|
const angle = firstArg.properties.find((p) => p.key.name === 'angle')?.value
|
||||||
const secondArgName = ['angledLineToX', 'angledLineToY'].includes(
|
const secondArgName = ['angledLineToX', 'angledLineToY'].includes(
|
||||||
callExpression?.callee?.name as TooTip
|
callExpression?.callee?.name as ToolTip
|
||||||
)
|
)
|
||||||
? 'to'
|
? 'to'
|
||||||
: 'length'
|
: 'length'
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { parser_wasm } from '../abstractSyntaxTree'
|
import { parse, SketchGroup, recast, initPromise } from '../wasm'
|
||||||
import { SketchGroup } from '../executor'
|
|
||||||
import {
|
import {
|
||||||
ConstraintType,
|
ConstraintType,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
} from './sketchcombos'
|
} from './sketchcombos'
|
||||||
import { recast } from '../recast'
|
|
||||||
import { initPromise } from '../rust'
|
|
||||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
||||||
import { Selection } from '../../useStore'
|
import { Selection } from '../../useStore'
|
||||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||||
@ -31,7 +28,7 @@ async function testingSwapSketchFnCall({
|
|||||||
type: 'default',
|
type: 'default',
|
||||||
range: [startIndex, startIndex + callToSwap.length],
|
range: [startIndex, startIndex + callToSwap.length],
|
||||||
}
|
}
|
||||||
const ast = parser_wasm(inputCode)
|
const ast = parse(inputCode)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const selections = {
|
const selections = {
|
||||||
codeBasedSelections: [range],
|
codeBasedSelections: [range],
|
||||||
@ -55,7 +52,8 @@ async function testingSwapSketchFnCall({
|
|||||||
|
|
||||||
describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
||||||
const bigExampleArr = [
|
const bigExampleArr = [
|
||||||
`const part001 = startSketchAt([0, 0])`,
|
`const part001 = startSketchOn('XY')`,
|
||||||
|
` |> startProfileAt([0, 0], %)`,
|
||||||
` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`,
|
` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`,
|
||||||
` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`,
|
` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`,
|
||||||
` |> angledLine({`,
|
` |> angledLine({`,
|
||||||
@ -280,7 +278,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
`const angledLineOfYLengthY = 0.89`,
|
`const angledLineOfYLengthY = 0.89`,
|
||||||
`const angledLineToXx = -1.86`,
|
`const angledLineToXx = -1.86`,
|
||||||
`const angledLineToYy = -0.76`,
|
`const angledLineToYy = -0.76`,
|
||||||
`const part001 = startSketchAt([0, 0])`,
|
`const part001 = startSketchOn('XY')`,
|
||||||
|
` |> startProfileAt([0, 0], %)`,
|
||||||
// ` |> rx(90, %)`,
|
// ` |> rx(90, %)`,
|
||||||
` |> lineTo([1, 1], %)`,
|
` |> lineTo([1, 1], %)`,
|
||||||
` |> line([lineX, 2.13], %)`,
|
` |> line([lineX, 2.13], %)`,
|
||||||
@ -374,14 +373,15 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
|
|
||||||
describe('testing getSketchSegmentIndexFromSourceRange', () => {
|
describe('testing getSketchSegmentIndexFromSourceRange', () => {
|
||||||
const code = `
|
const code = `
|
||||||
const part001 = startSketchAt([0, 0.04]) // segment-in-start
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0.04], %) // segment-in-start
|
||||||
|> line([0, 0.4], %)
|
|> line([0, 0.4], %)
|
||||||
|> xLine(3.48, %)
|
|> xLine(3.48, %)
|
||||||
|> line([2.14, 1.35], %) // normal-segment
|
|> line([2.14, 1.35], %) // normal-segment
|
||||||
|> xLine(3.54, %)
|
|> xLine(3.54, %)
|
||||||
show(part001)`
|
show(part001)`
|
||||||
it('normal case works', async () => {
|
it('normal case works', async () => {
|
||||||
const programMemory = await enginelessExecutor(parser_wasm(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
const index = code.indexOf('// normal-segment') - 7
|
const index = code.indexOf('// normal-segment') - 7
|
||||||
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
||||||
programMemory.root['part001'] as SketchGroup,
|
programMemory.root['part001'] as SketchGroup,
|
||||||
@ -395,7 +395,7 @@ show(part001)`
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('verify it works when the segment is in the `start` property', async () => {
|
it('verify it works when the segment is in the `start` property', async () => {
|
||||||
const programMemory = await enginelessExecutor(parser_wasm(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
const index = code.indexOf('// segment-in-start') - 7
|
const index = code.indexOf('// segment-in-start') - 7
|
||||||
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
||||||
programMemory.root['part001'] as SketchGroup,
|
programMemory.root['part001'] as SketchGroup,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { TooTip, toolTips } from '../../useStore'
|
import { ToolTip, toolTips } from '../../useStore'
|
||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
} from '../abstractSyntaxTreeTypes'
|
SketchGroup,
|
||||||
import { SketchGroup, SourceRange, Path } from '../executor'
|
SourceRange,
|
||||||
|
Path,
|
||||||
|
} from '../wasm'
|
||||||
|
|
||||||
export function getSketchSegmentFromSourceRange(
|
export function getSketchSegmentFromSourceRange(
|
||||||
sketchGroup: SketchGroup,
|
sketchGroup: SketchGroup,
|
||||||
@ -67,7 +69,10 @@ export function isSketchVariablesLinked(
|
|||||||
return false
|
return false
|
||||||
const firstCallExp = // first in pipe expression or just the call expression
|
const firstCallExp = // first in pipe expression or just the call expression
|
||||||
init?.type === 'CallExpression' ? init : (init?.body[0] as CallExpression)
|
init?.type === 'CallExpression' ? init : (init?.body[0] as CallExpression)
|
||||||
if (!firstCallExp || !toolTips.includes(firstCallExp?.callee?.name as TooTip))
|
if (
|
||||||
|
!firstCallExp ||
|
||||||
|
!toolTips.includes(firstCallExp?.callee?.name as ToolTip)
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
// convention for sketch fns is that the second argument is the sketch group
|
// convention for sketch fns is that the second argument is the sketch group
|
||||||
const secondArg = firstCallExp?.arguments[1]
|
const secondArg = firstCallExp?.arguments[1]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { parser_wasm } from '../abstractSyntaxTree'
|
import { parse, Value, recast, initPromise } from '../wasm'
|
||||||
import { Value } from '../abstractSyntaxTreeTypes'
|
|
||||||
import {
|
import {
|
||||||
getConstraintType,
|
getConstraintType,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
@ -8,10 +7,8 @@ import {
|
|||||||
ConstraintType,
|
ConstraintType,
|
||||||
getConstraintLevelFromSourceRange,
|
getConstraintLevelFromSourceRange,
|
||||||
} from './sketchcombos'
|
} from './sketchcombos'
|
||||||
import { initPromise } from '../rust'
|
import { Selections, ToolTip } from '../../useStore'
|
||||||
import { Selections, TooTip } from '../../useStore'
|
|
||||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||||
import { recast } from '../../lang/recast'
|
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
@ -63,20 +60,20 @@ describe('testing getConstraintType', () => {
|
|||||||
function getConstraintTypeFromSourceHelper(
|
function getConstraintTypeFromSourceHelper(
|
||||||
code: string
|
code: string
|
||||||
): ReturnType<typeof getConstraintType> {
|
): ReturnType<typeof getConstraintType> {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const args = (ast.body[0] as any).expression.arguments[0].elements as [
|
const args = (ast.body[0] as any).expression.arguments[0].elements as [
|
||||||
Value,
|
Value,
|
||||||
Value
|
Value
|
||||||
]
|
]
|
||||||
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
|
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||||
return getConstraintType(args, fnName)
|
return getConstraintType(args, fnName)
|
||||||
}
|
}
|
||||||
function getConstraintTypeFromSourceHelper2(
|
function getConstraintTypeFromSourceHelper2(
|
||||||
code: string
|
code: string
|
||||||
): ReturnType<typeof getConstraintType> {
|
): ReturnType<typeof getConstraintType> {
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const arg = (ast.body[0] as any).expression.arguments[0] as Value
|
const arg = (ast.body[0] as any).expression.arguments[0] as Value
|
||||||
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
|
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||||
return getConstraintType(arg, fnName)
|
return getConstraintType(arg, fnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +92,8 @@ const myVar2 = 5
|
|||||||
const myVar3 = 6
|
const myVar3 = 6
|
||||||
const myAng = 40
|
const myAng = 40
|
||||||
const myAng2 = 134
|
const myAng2 = 134
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([1, 3.82], %) // ln-should-get-tag
|
|> line([1, 3.82], %) // ln-should-get-tag
|
||||||
|> lineTo([myVar, 1], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|
|> lineTo([myVar, 1], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|
||||||
|> lineTo([1, myVar], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
|
|> lineTo([1, myVar], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
|
||||||
@ -131,7 +129,8 @@ const myVar2 = 5
|
|||||||
const myVar3 = 6
|
const myVar3 = 6
|
||||||
const myAng = 40
|
const myAng = 40
|
||||||
const myAng2 = 134
|
const myAng2 = 134
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|
||||||
|> angledLineToX([
|
|> angledLineToX([
|
||||||
-angleToMatchLengthX('seg01', myVar, %),
|
-angleToMatchLengthX('seg01', myVar, %),
|
||||||
@ -199,7 +198,7 @@ const part001 = startSketchAt([0, 0])
|
|||||||
show(part001)
|
show(part001)
|
||||||
`
|
`
|
||||||
it('should transform the ast', async () => {
|
it('should transform the ast', async () => {
|
||||||
const ast = parser_wasm(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((ln) => ln.includes('//'))
|
.filter((ln) => ln.includes('//'))
|
||||||
@ -234,7 +233,8 @@ describe('testing transformAstForSketchLines for vertical and horizontal constra
|
|||||||
const inputScript = `const myVar = 2
|
const inputScript = `const myVar = 2
|
||||||
const myVar2 = 12
|
const myVar2 = 12
|
||||||
const myVar3 = -10
|
const myVar3 = -10
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
||||||
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
||||||
@ -262,7 +262,8 @@ show(part001)
|
|||||||
const expectModifiedScript = `const myVar = 2
|
const expectModifiedScript = `const myVar = 2
|
||||||
const myVar2 = 12
|
const myVar2 = 12
|
||||||
const myVar3 = -10
|
const myVar3 = -10
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> xLine(-6.28, %) // select for horizontal constraint 1
|
|> xLine(-6.28, %) // select for horizontal constraint 1
|
||||||
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
||||||
@ -286,7 +287,7 @@ const part001 = startSketchAt([0, 0])
|
|||||||
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||||
show(part001)
|
show(part001)
|
||||||
`
|
`
|
||||||
const ast = parser_wasm(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((ln) => ln.includes('// select for horizontal constraint'))
|
.filter((ln) => ln.includes('// select for horizontal constraint'))
|
||||||
@ -320,7 +321,8 @@ show(part001)
|
|||||||
const expectModifiedScript = `const myVar = 2
|
const expectModifiedScript = `const myVar = 2
|
||||||
const myVar2 = 12
|
const myVar2 = 12
|
||||||
const myVar3 = -10
|
const myVar3 = -10
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
||||||
|> yLine(myVar, %) // select for vertical constraint 1
|
|> yLine(myVar, %) // select for vertical constraint 1
|
||||||
@ -344,7 +346,7 @@ const part001 = startSketchAt([0, 0])
|
|||||||
|> yLineTo(myVar, %) // select for vertical constraint 10
|
|> yLineTo(myVar, %) // select for vertical constraint 10
|
||||||
show(part001)
|
show(part001)
|
||||||
`
|
`
|
||||||
const ast = parser_wasm(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((ln) => ln.includes('// select for vertical constraint'))
|
.filter((ln) => ln.includes('// select for vertical constraint'))
|
||||||
@ -379,7 +381,8 @@ show(part001)
|
|||||||
describe('testing transformAstForSketchLines for vertical and horizontal distance constraints', () => {
|
describe('testing transformAstForSketchLines for vertical and horizontal distance constraints', () => {
|
||||||
describe('testing setHorzDistance for line', () => {
|
describe('testing setHorzDistance for line', () => {
|
||||||
const inputScript = `const myVar = 1
|
const inputScript = `const myVar = 1
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([0.31, 1.67], %) // base selection
|
|> line([0.31, 1.67], %) // base selection
|
||||||
|> line([0.45, 1.46], %)
|
|> line([0.45, 1.46], %)
|
||||||
|> line([0.45, 1.46], %) // free
|
|> line([0.45, 1.46], %) // free
|
||||||
@ -435,7 +438,7 @@ async function helperThing(
|
|||||||
linesOfInterest: string[],
|
linesOfInterest: string[],
|
||||||
constraint: ConstraintType
|
constraint: ConstraintType
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const ast = parser_wasm(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((ln) =>
|
.filter((ln) =>
|
||||||
@ -480,7 +483,8 @@ const baseThickHalf = baseThick / 2
|
|||||||
const halfHeight = totalHeight / 2
|
const halfHeight = totalHeight / 2
|
||||||
const halfArmAngle = armAngle / 2
|
const halfArmAngle = armAngle / 2
|
||||||
|
|
||||||
const part001 = startSketchAt([-0.01, -0.05])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-0.01, -0.05], %)
|
||||||
|> line([0.01, 0.94 + 0], %) // partial
|
|> line([0.01, 0.94 + 0], %) // partial
|
||||||
|> xLine(3.03, %) // partial
|
|> xLine(3.03, %) // partial
|
||||||
|> angledLine({
|
|> angledLine({
|
||||||
@ -498,7 +502,7 @@ const part001 = startSketchAt([-0.01, -0.05])
|
|||||||
|> xLine(-3.43 + 0, %) // full
|
|> xLine(-3.43 + 0, %) // full
|
||||||
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full
|
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full
|
||||||
show(part001)`
|
show(part001)`
|
||||||
const ast = parser_wasm(code)
|
const ast = parse(code)
|
||||||
const constraintLevels: ReturnType<
|
const constraintLevels: ReturnType<
|
||||||
typeof getConstraintLevelFromSourceRange
|
typeof getConstraintLevelFromSourceRange
|
||||||
>[] = ['full', 'partial', 'free']
|
>[] = ['full', 'partial', 'free']
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { TransformCallback } from './stdTypes'
|
import { TransformCallback } from './stdTypes'
|
||||||
import { Selections, toolTips, TooTip, Selection } from '../../useStore'
|
import { Selections, toolTips, ToolTip, Selection } from '../../useStore'
|
||||||
import {
|
import {
|
||||||
CallExpression,
|
CallExpression,
|
||||||
Program,
|
Program,
|
||||||
Value,
|
Value,
|
||||||
BinaryPart,
|
BinaryPart,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
} from '../abstractSyntaxTreeTypes'
|
PathToNode,
|
||||||
|
ProgramMemory,
|
||||||
|
} from '../wasm'
|
||||||
import {
|
import {
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodeFromPathCurry,
|
getNodeFromPathCurry,
|
||||||
@ -25,10 +27,8 @@ import {
|
|||||||
giveSketchFnCallTag,
|
giveSketchFnCallTag,
|
||||||
} from '../modifyAst'
|
} from '../modifyAst'
|
||||||
import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
|
import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
|
||||||
import { PathToNode, ProgramMemory } from '../executor'
|
|
||||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
||||||
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
|
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
|
||||||
import { MemoryItem } from 'wasm-lib/kcl/bindings/MemoryItem'
|
|
||||||
|
|
||||||
type LineInputsType =
|
type LineInputsType =
|
||||||
| 'xAbsolute'
|
| 'xAbsolute'
|
||||||
@ -54,7 +54,7 @@ export type ConstraintType =
|
|||||||
| 'setAngleBetween'
|
| 'setAngleBetween'
|
||||||
|
|
||||||
function createCallWrapper(
|
function createCallWrapper(
|
||||||
a: TooTip,
|
a: ToolTip,
|
||||||
val: [Value, Value] | Value,
|
val: [Value, Value] | Value,
|
||||||
tag?: Value,
|
tag?: Value,
|
||||||
valueUsedInTransform?: number
|
valueUsedInTransform?: number
|
||||||
@ -101,7 +101,7 @@ function intersectCallWrapper({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TransformInfo = {
|
export type TransformInfo = {
|
||||||
tooltip: TooTip
|
tooltip: ToolTip
|
||||||
createNode: (a: {
|
createNode: (a: {
|
||||||
varValA: Value // x / angle
|
varValA: Value // x / angle
|
||||||
varValB: Value // y / length or x y for angledLineOfXlength etc
|
varValB: Value // y / length or x y for angledLineOfXlength etc
|
||||||
@ -112,7 +112,7 @@ export type TransformInfo = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TransformMap = {
|
type TransformMap = {
|
||||||
[key in TooTip]?: {
|
[key in ToolTip]?: {
|
||||||
[key in LineInputsType | 'free']?: {
|
[key in LineInputsType | 'free']?: {
|
||||||
[key in ConstraintType]?: TransformInfo
|
[key in ConstraintType]?: TransformInfo
|
||||||
}
|
}
|
||||||
@ -1095,12 +1095,12 @@ export function getRemoveConstraintsTransform(
|
|||||||
sketchFnExp: CallExpression,
|
sketchFnExp: CallExpression,
|
||||||
constraintType: ConstraintType
|
constraintType: ConstraintType
|
||||||
): TransformInfo | false {
|
): TransformInfo | false {
|
||||||
let name = sketchFnExp.callee.name as TooTip
|
let name = sketchFnExp.callee.name as ToolTip
|
||||||
if (!toolTips.includes(name)) {
|
if (!toolTips.includes(name)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const xyLineMap: {
|
const xyLineMap: {
|
||||||
[key in TooTip]?: TooTip
|
[key in ToolTip]?: ToolTip
|
||||||
} = {
|
} = {
|
||||||
xLine: 'line',
|
xLine: 'line',
|
||||||
yLine: 'line',
|
yLine: 'line',
|
||||||
@ -1137,27 +1137,18 @@ export function getRemoveConstraintsTransform(
|
|||||||
|
|
||||||
// check if the function is locked down and so can't be transformed
|
// check if the function is locked down and so can't be transformed
|
||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
if (Array.isArray(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
const [a, b] = firstArg.val
|
return transformInfo
|
||||||
if (a?.type !== 'Literal' || b?.type !== 'Literal') {
|
|
||||||
return transformInfo
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (firstArg.val?.type !== 'Literal') {
|
|
||||||
return transformInfo
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
const isTwoValFree =
|
||||||
Array.isArray(firstArg.val) &&
|
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
firstArg.val?.[0]?.type === 'Literal' &&
|
|
||||||
firstArg.val?.[1]?.type === 'Literal'
|
|
||||||
if (isTwoValFree) {
|
if (isTwoValFree) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const isOneValFree =
|
const isOneValFree =
|
||||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
if (isOneValFree) {
|
if (isOneValFree) {
|
||||||
return transformInfo
|
return transformInfo
|
||||||
}
|
}
|
||||||
@ -1176,37 +1167,24 @@ function getTransformMapPath(
|
|||||||
constraintType: ConstraintType
|
constraintType: ConstraintType
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
toolTip: TooTip
|
toolTip: ToolTip
|
||||||
lineInputType: LineInputsType | 'free'
|
lineInputType: LineInputsType | 'free'
|
||||||
constraintType: ConstraintType
|
constraintType: ConstraintType
|
||||||
}
|
}
|
||||||
| false {
|
| false {
|
||||||
const name = sketchFnExp.callee.name as TooTip
|
const name = sketchFnExp.callee.name as ToolTip
|
||||||
if (!toolTips.includes(name)) {
|
if (!toolTips.includes(name)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function is locked down and so can't be transformed
|
// check if the function is locked down and so can't be transformed
|
||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
if (Array.isArray(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
const [a, b] = firstArg.val
|
return false
|
||||||
if (a?.type !== 'Literal' && b?.type !== 'Literal') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (firstArg.val?.type !== 'Literal') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
if (isLiteralArrayOrStatic(firstArg.val)) {
|
||||||
Array.isArray(firstArg.val) &&
|
|
||||||
firstArg.val?.[0]?.type === 'Literal' &&
|
|
||||||
firstArg.val?.[1]?.type === 'Literal'
|
|
||||||
const isOneValFree =
|
|
||||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
|
||||||
if (isTwoValFree || isOneValFree) {
|
|
||||||
const info = transformMap?.[name]?.free?.[constraintType]
|
const info = transformMap?.[name]?.free?.[constraintType]
|
||||||
if (info)
|
if (info)
|
||||||
return {
|
return {
|
||||||
@ -1247,7 +1225,7 @@ export function getTransformInfo(
|
|||||||
|
|
||||||
export function getConstraintType(
|
export function getConstraintType(
|
||||||
val: Value | [Value, Value] | [Value, Value, Value],
|
val: Value | [Value, Value] | [Value, Value, Value],
|
||||||
fnName: TooTip
|
fnName: ToolTip
|
||||||
): LineInputsType | null {
|
): LineInputsType | null {
|
||||||
// this function assumes that for two val sketch functions that one arg is locked down not both
|
// this function assumes that for two val sketch functions that one arg is locked down not both
|
||||||
// and for one val sketch functions that the arg is NOT locked down
|
// and for one val sketch functions that the arg is NOT locked down
|
||||||
@ -1260,7 +1238,7 @@ export function getConstraintType(
|
|||||||
if (fnName === 'xLineTo') return 'yAbsolute'
|
if (fnName === 'xLineTo') return 'yAbsolute'
|
||||||
if (fnName === 'yLineTo') return 'xAbsolute'
|
if (fnName === 'yLineTo') return 'xAbsolute'
|
||||||
} else {
|
} else {
|
||||||
const isFirstArgLockedDown = val?.[0]?.type !== 'Literal'
|
const isFirstArgLockedDown = isNotLiteralArrayOrStatic(val[0])
|
||||||
if (fnName === 'line')
|
if (fnName === 'line')
|
||||||
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
|
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
|
||||||
if (fnName === 'lineTo')
|
if (fnName === 'lineTo')
|
||||||
@ -1301,7 +1279,7 @@ export function getTransformInfos(
|
|||||||
}) as TransformInfo[]
|
}) as TransformInfo[]
|
||||||
return theTransforms
|
return theTransforms
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log('error', error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1467,7 +1445,7 @@ export function transformAstSketchLines({
|
|||||||
programMemory,
|
programMemory,
|
||||||
sourceRange: range,
|
sourceRange: range,
|
||||||
referencedSegment,
|
referencedSegment,
|
||||||
fnName: transformTo || (callExp.callee.name as TooTip),
|
fnName: transformTo || (callExp.callee.name as ToolTip),
|
||||||
to,
|
to,
|
||||||
from,
|
from,
|
||||||
createCallback: callBack({
|
createCallback: callBack({
|
||||||
@ -1533,29 +1511,52 @@ export function getConstraintLevelFromSourceRange(
|
|||||||
getNodePathFromSourceRange(ast, cursorRange),
|
getNodePathFromSourceRange(ast, cursorRange),
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
)
|
)
|
||||||
const name = sketchFnExp?.callee?.name as TooTip
|
const name = sketchFnExp?.callee?.name as ToolTip
|
||||||
if (!toolTips.includes(name)) return 'free'
|
if (!toolTips.includes(name)) return 'free'
|
||||||
|
|
||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
|
|
||||||
// check if the function is fully constrained
|
// check if the function is fully constrained
|
||||||
if (Array.isArray(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
const [a, b] = firstArg.val
|
return 'full'
|
||||||
if (a?.type !== 'Literal' && b?.type !== 'Literal') return 'full'
|
|
||||||
} else {
|
|
||||||
if (firstArg.val?.type !== 'Literal') return 'full'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
const isTwoValFree =
|
const isTwoValFree =
|
||||||
Array.isArray(firstArg.val) &&
|
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
firstArg.val?.[0]?.type === 'Literal' &&
|
|
||||||
firstArg.val?.[1]?.type === 'Literal'
|
|
||||||
const isOneValFree =
|
const isOneValFree =
|
||||||
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
|
|
||||||
if (isTwoValFree) return 'free'
|
if (isTwoValFree) return 'free'
|
||||||
if (isOneValFree) return 'partial'
|
if (isOneValFree) return 'partial'
|
||||||
|
|
||||||
return 'partial'
|
return 'partial'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isLiteralArrayOrStatic(
|
||||||
|
val: Value | [Value, Value] | [Value, Value, Value] | undefined
|
||||||
|
): boolean {
|
||||||
|
if (!val) return false
|
||||||
|
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
const [a, b] = val
|
||||||
|
return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
val.type === 'Literal' ||
|
||||||
|
(val.type === 'UnaryExpression' && val.argument.type === 'Literal')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotLiteralArrayOrStatic(
|
||||||
|
val: Value | [Value, Value] | [Value, Value, Value]
|
||||||
|
): boolean {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
const [a, b] = val
|
||||||
|
return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
(val.type !== 'Literal' && val.type !== 'UnaryExpression') ||
|
||||||
|
(val.type === 'UnaryExpression' && val.argument.type !== 'Literal')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { parser_wasm } from '../abstractSyntaxTree'
|
import { parse, initPromise } from '../wasm'
|
||||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||||
import { initPromise } from '../rust'
|
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
describe('testing angledLineThatIntersects', () => {
|
describe('testing angledLineThatIntersects', () => {
|
||||||
it('angledLineThatIntersects should intersect with another line', async () => {
|
it('angledLineThatIntersects should intersect with another line', async () => {
|
||||||
const code = (offset: string) => `const part001 = startSketchAt([0, 0])
|
const code = (offset: string) => `const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo({to:[2, 2], tag: "yo"}, %)
|
|> lineTo({to:[2, 2], tag: "yo"}, %)
|
||||||
|> lineTo([3, 1], %)
|
|> lineTo([3, 1], %)
|
||||||
|> angledLineThatIntersects({
|
|> angledLineThatIntersects({
|
||||||
@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
|
|||||||
}, %)
|
}, %)
|
||||||
const intersect = segEndX('yo2', part001)
|
const intersect = segEndX('yo2', part001)
|
||||||
show(part001)`
|
show(part001)`
|
||||||
const { root } = await enginelessExecutor(parser_wasm(code('-1')))
|
const { root } = await enginelessExecutor(parse(code('-1')))
|
||||||
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
|
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
|
||||||
const { root: noOffset } = await enginelessExecutor(parser_wasm(code('0')))
|
const { root: noOffset } = await enginelessExecutor(parse(code('0')))
|
||||||
expect(noOffset.intersect.value).toBeCloseTo(1)
|
expect(noOffset.intersect.value).toBeCloseTo(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { ProgramMemory, Path, SourceRange } from '../executor'
|
import {
|
||||||
import { Program, Value } from '../abstractSyntaxTreeTypes'
|
ProgramMemory,
|
||||||
import { TooTip } from '../../useStore'
|
Path,
|
||||||
import { PathToNode } from '../executor'
|
SourceRange,
|
||||||
|
Program,
|
||||||
|
Value,
|
||||||
|
PathToNode,
|
||||||
|
} from '../wasm'
|
||||||
|
import { ToolTip } from '../../useStore'
|
||||||
import { EngineCommandManager } from './engineConnection'
|
import { EngineCommandManager } from './engineConnection'
|
||||||
|
|
||||||
export interface InternalFirstArg {
|
export interface InternalFirstArg {
|
||||||
@ -45,7 +50,7 @@ export type TransformCallback = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SketchCallTransfromMap = {
|
export type SketchCallTransfromMap = {
|
||||||
[key in TooTip]: TransformCallback
|
[key in ToolTip]: TransformCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SketchLineHelper {
|
export interface SketchLineHelper {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { lexer, asyncLexer } from './tokeniser'
|
import { lexer, initPromise } from './wasm'
|
||||||
import { initPromise } from './rust'
|
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
@ -10,9 +9,9 @@ describe('testing lexer', () => {
|
|||||||
const code3 = `const yo = 45 /* this is a comment
|
const code3 = `const yo = 45 /* this is a comment
|
||||||
const ya = 6 */
|
const ya = 6 */
|
||||||
const yi=45`
|
const yi=45`
|
||||||
expect(await asyncLexer(code)).toEqual(lexer(code))
|
expect(lexer(code)).toEqual(lexer(code))
|
||||||
expect(await asyncLexer(code2)).toEqual(lexer(code2))
|
expect(lexer(code2)).toEqual(lexer(code2))
|
||||||
expect(await asyncLexer(code3)).toEqual(lexer(code3))
|
expect(lexer(code3)).toEqual(lexer(code3))
|
||||||
})
|
})
|
||||||
it('test lexer', () => {
|
it('test lexer', () => {
|
||||||
expect(stringSummaryLexer('1 + 2')).toEqual([
|
expect(stringSummaryLexer('1 + 2')).toEqual([
|
||||||
@ -131,10 +130,12 @@ const yi=45`
|
|||||||
})
|
})
|
||||||
it('test negative and decimal numbers', () => {
|
it('test negative and decimal numbers', () => {
|
||||||
expect(stringSummaryLexer('-1')).toEqual([
|
expect(stringSummaryLexer('-1')).toEqual([
|
||||||
"number '-1' from 0 to 2",
|
"operator '-' from 0 to 1",
|
||||||
|
"number '1' from 1 to 2",
|
||||||
])
|
])
|
||||||
expect(stringSummaryLexer('-1.5')).toEqual([
|
expect(stringSummaryLexer('-1.5')).toEqual([
|
||||||
"number '-1.5' from 0 to 4",
|
"operator '-' from 0 to 1",
|
||||||
|
"number '1.5' from 1 to 4",
|
||||||
])
|
])
|
||||||
expect(stringSummaryLexer('1.5')).toEqual([
|
expect(stringSummaryLexer('1.5')).toEqual([
|
||||||
"number '1.5' from 0 to 3",
|
"number '1.5' from 0 to 3",
|
||||||
@ -158,10 +159,12 @@ const yi=45`
|
|||||||
"whitespace ' ' from 3 to 4",
|
"whitespace ' ' from 3 to 4",
|
||||||
"operator '+' from 4 to 5",
|
"operator '+' from 4 to 5",
|
||||||
"whitespace ' ' from 5 to 6",
|
"whitespace ' ' from 5 to 6",
|
||||||
"number '-2.5' from 6 to 10",
|
"operator '-' from 6 to 7",
|
||||||
|
"number '2.5' from 7 to 10",
|
||||||
])
|
])
|
||||||
expect(stringSummaryLexer('-1.5 + 2.5')).toEqual([
|
expect(stringSummaryLexer('-1.5 + 2.5')).toEqual([
|
||||||
"number '-1.5' from 0 to 4",
|
"operator '-' from 0 to 1",
|
||||||
|
"number '1.5' from 1 to 4",
|
||||||
"whitespace ' ' from 4 to 5",
|
"whitespace ' ' from 4 to 5",
|
||||||
"operator '+' from 5 to 6",
|
"operator '+' from 5 to 6",
|
||||||
"whitespace ' ' from 6 to 7",
|
"whitespace ' ' from 6 to 7",
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import { lexer_js } from '../wasm-lib/pkg/wasm_lib'
|
|
||||||
import { initPromise } from './rust'
|
|
||||||
import { Token } from '../wasm-lib/kcl/bindings/Token'
|
|
||||||
|
|
||||||
export type { Token } from '../wasm-lib/kcl/bindings/Token'
|
|
||||||
|
|
||||||
export async function asyncLexer(str: string): Promise<Token[]> {
|
|
||||||
await initPromise
|
|
||||||
try {
|
|
||||||
const tokens: Token[] = lexer_js(str)
|
|
||||||
return tokens
|
|
||||||
} catch (e) {
|
|
||||||
// TODO: do something real with the error.
|
|
||||||
console.log('lexer', e)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lexer(str: string): Token[] {
|
|
||||||
try {
|
|
||||||
const tokens: Token[] = lexer_js(str)
|
|
||||||
return tokens
|
|
||||||
} catch (e) {
|
|
||||||
// TODO: do something real with the error.
|
|
||||||
console.log('lexer', e)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user