Compare commits
28 Commits
kurt-model
...
v0.25.1
Author | SHA1 | Date | |
---|---|---|---|
e61516f3c3 | |||
e2eeec37ad | |||
d7fcc128aa | |||
cf266b17c1 | |||
b3a1796da9 | |||
39b9a6b2c4 | |||
6ba4fa305c | |||
1d043899c8 | |||
cb8a087d89 | |||
f2eb7b57b8 | |||
eba653930f | |||
3deb5c689a | |||
11ebe11111 | |||
9538ffb8ec | |||
55d1da226f | |||
2bfde64bf1 | |||
7cb9a2efd9 | |||
57e85d7fd0 | |||
ca4a442cce | |||
46eef39d53 | |||
dbc5f7b11f | |||
6797331c9d | |||
cc80a2da3d | |||
54fb9c903a | |||
e63597458a | |||
e15c38fa23 | |||
906ca65611 | |||
805b9f48e5 |
@ -2,7 +2,9 @@ NODE_ENV=development
|
|||||||
DEV=true
|
DEV=true
|
||||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
||||||
|
BASE_URL=https://api.dev.zoo.dev
|
||||||
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
|
||||||
|
#VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
||||||
|
105
.github/workflows/build-test-publish-apps.yml
vendored
@ -44,7 +44,7 @@ jobs:
|
|||||||
|
|
||||||
# TODO: see if we can fetch from main instead if no diff at src/wasm-lib
|
# TODO: see if we can fetch from main instead if no diff at src/wasm-lib
|
||||||
- name: Run build:wasm
|
- name: Run build:wasm
|
||||||
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
|
run: "yarn build:wasm"
|
||||||
|
|
||||||
- name: Set nightly version
|
- name: Set nightly version
|
||||||
if: github.event_name == 'schedule'
|
if: github.event_name == 'schedule'
|
||||||
@ -81,8 +81,6 @@ jobs:
|
|||||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
CSC_FOR_PULL_REQUEST: true
|
CSC_FOR_PULL_REQUEST: true
|
||||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
||||||
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
||||||
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
||||||
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||||
WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D
|
WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D
|
||||||
@ -142,37 +140,12 @@ jobs:
|
|||||||
- name: List artifacts in out/
|
- name: List artifacts in out/
|
||||||
run: ls -R out
|
run: ls -R out
|
||||||
|
|
||||||
- name: Prepare the tauri update bundles (macOS)
|
|
||||||
if: ${{ env.BUILD_RELEASE && matrix.os == 'macos-14' }}
|
|
||||||
run: |
|
|
||||||
for ARCH in arm64 x64; do
|
|
||||||
TAURI_DIR=out/tauri/$VERSION/macos
|
|
||||||
TEMP_DIR=temp/$ARCH
|
|
||||||
mkdir -p $TAURI_DIR
|
|
||||||
mkdir -p $TEMP_DIR
|
|
||||||
unzip out/*-$ARCH-mac.zip -d $TEMP_DIR
|
|
||||||
tar -czvf "$TAURI_DIR/Zoo Modeling App-$ARCH.app.tar.gz" -C $TEMP_DIR "Zoo Modeling App.app"
|
|
||||||
yarn tauri signer sign "$TAURI_DIR/Zoo Modeling App-$ARCH.app.tar.gz"
|
|
||||||
done
|
|
||||||
ls -R out
|
|
||||||
|
|
||||||
- name: Prepare the tauri update bundles (Windows)
|
|
||||||
if: ${{ env.BUILD_RELEASE && matrix.os == 'windows-2022' }}
|
|
||||||
run: |
|
|
||||||
$env:TAURI_DIR="out/tauri/${env:VERSION}/nsis"
|
|
||||||
mkdir -p ${env:TAURI_DIR}
|
|
||||||
$env:OUT_FILE="${env:TAURI_DIR}/Zoo Modeling App_${env:VERSION_NO_V}_x64-setup.nsis.zip"
|
|
||||||
7z a -mm=Copy "${env:OUT_FILE}" ./out/*-x64-win.exe
|
|
||||||
yarn tauri signer sign "${env:OUT_FILE}"
|
|
||||||
ls -R out
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: out-${{ matrix.os }}
|
name: out-${{ matrix.os }}
|
||||||
path: |
|
path: |
|
||||||
out/Zoo*.*
|
out/Zoo*.*
|
||||||
out/latest*.yml
|
out/latest*.yml
|
||||||
out/tauri
|
|
||||||
|
|
||||||
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
|
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
|
||||||
|
|
||||||
@ -192,8 +165,6 @@ jobs:
|
|||||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
||||||
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
|
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
|
||||||
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
|
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
|
||||||
BUCKET_DIR_TAURI: 'dl.kittycad.io/releases/modeling-app/tauri-compat'
|
|
||||||
WEBSITE_DIR_TAURI: 'dl.zoo.dev/releases/modeling-app/tauri-compat'
|
|
||||||
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@ -212,7 +183,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: out-ubuntu-22.04
|
name: out-ubuntu-22.04
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Generate the download static endpoint
|
- name: Generate the download static endpoint
|
||||||
run: |
|
run: |
|
||||||
RELEASE_DIR=https://${WEBSITE_DIR}
|
RELEASE_DIR=https://${WEBSITE_DIR}
|
||||||
@ -253,44 +224,6 @@ jobs:
|
|||||||
}' > last_download.json
|
}' > last_download.json
|
||||||
cat last_download.json
|
cat last_download.json
|
||||||
|
|
||||||
- name: Generate the update static endpoint for tauri
|
|
||||||
run: |
|
|
||||||
TAURI_DIR=out/tauri/$VERSION
|
|
||||||
MAC_ARM64_SIG=`cat $TAURI_DIR/macos/*-arm64.app.tar.gz.sig`
|
|
||||||
MAC_X64_SIG=`cat $TAURI_DIR/macos/*-x64.app.tar.gz.sig`
|
|
||||||
WINDOWS_SIG=`cat $TAURI_DIR/nsis/*.nsis.zip.sig`
|
|
||||||
RELEASE_DIR=https://${WEBSITE_DIR_TAURI}/${VERSION}
|
|
||||||
jq --null-input \
|
|
||||||
--arg version "${VERSION}" \
|
|
||||||
--arg pub_date "${PUB_DATE}" \
|
|
||||||
--arg notes "${NOTES}" \
|
|
||||||
--arg mac_arm64_sig "$MAC_ARM64_SIG" \
|
|
||||||
--arg mac_arm64_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}-arm64.app.tar.gz" \
|
|
||||||
--arg mac_x64_sig "$MAC_X64_SIG" \
|
|
||||||
--arg mac_x64_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}-x64.app.tar.gz" \
|
|
||||||
--arg windows_sig "$WINDOWS_SIG" \
|
|
||||||
--arg windows_url "$RELEASE_DIR/nsis/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64-setup.nsis.zip" \
|
|
||||||
'{
|
|
||||||
"version": $version,
|
|
||||||
"pub_date": $pub_date,
|
|
||||||
"notes": $notes,
|
|
||||||
"platforms": {
|
|
||||||
"darwin-x86_64": {
|
|
||||||
"signature": $mac_x64_sig,
|
|
||||||
"url": $mac_x64_url
|
|
||||||
},
|
|
||||||
"darwin-aarch64": {
|
|
||||||
"signature": $mac_arm64_sig,
|
|
||||||
"url": $mac_arm64_url
|
|
||||||
},
|
|
||||||
"windows-x86_64": {
|
|
||||||
"signature": $windows_sig,
|
|
||||||
"url": $windows_url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}' > last_update.json
|
|
||||||
cat last_update.json
|
|
||||||
|
|
||||||
- name: List artifacts
|
- name: List artifacts
|
||||||
run: "ls -R out"
|
run: "ls -R out"
|
||||||
|
|
||||||
@ -312,13 +245,31 @@ jobs:
|
|||||||
parent: false
|
parent: false
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
|
|
||||||
|
# TODO: remove workaround introduced in https://github.com/KittyCAD/modeling-app/issues/3817
|
||||||
|
- name: Upload release files to public bucket (test/electron-builder workaround)
|
||||||
|
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||||
|
with:
|
||||||
|
path: out
|
||||||
|
glob: 'Zoo*'
|
||||||
|
parent: false
|
||||||
|
destination: '${{ env.BUCKET_DIR }}/test/electron-builder'
|
||||||
|
|
||||||
- name: Upload update endpoint to public bucket
|
- name: Upload update endpoint to public bucket
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||||
with:
|
with:
|
||||||
path: out
|
path: out
|
||||||
glob: 'latest*'
|
glob: 'latest*'
|
||||||
parent: false
|
parent: false
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
|
|
||||||
|
# TODO: remove workaround introduced in https://github.com/KittyCAD/modeling-app/issues/3817
|
||||||
|
- name: Upload update endpoint to public bucket (test/electron-builder workaround)
|
||||||
|
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||||
|
with:
|
||||||
|
path: out
|
||||||
|
glob: 'latest*'
|
||||||
|
parent: false
|
||||||
|
destination: '${{ env.BUCKET_DIR }}/test/electron-builder'
|
||||||
|
|
||||||
- name: Upload download endpoint to public bucket
|
- name: Upload download endpoint to public bucket
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||||
@ -326,20 +277,6 @@ jobs:
|
|||||||
path: last_download.json
|
path: last_download.json
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
|
|
||||||
- name: Upload release files to public bucket for tauri
|
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
|
||||||
with:
|
|
||||||
path: "out/tauri/${{ env.VERSION }}"
|
|
||||||
glob: '*/Zoo*'
|
|
||||||
parent: false
|
|
||||||
destination: ${{ env.BUCKET_DIR_TAURI }}/${{ env.VERSION }}
|
|
||||||
|
|
||||||
- name: Upload update endpoint to public bucket for tauri
|
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
|
||||||
with:
|
|
||||||
path: last_update.json
|
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
|
||||||
|
|
||||||
- name: Upload release files to Github
|
- name: Upload release files to Github
|
||||||
if: ${{ github.event_name == 'release' }}
|
if: ${{ github.event_name == 'release' }}
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
|
3
.github/workflows/cargo-clippy.yml
vendored
@ -28,6 +28,7 @@ jobs:
|
|||||||
dir: ['src/wasm-lib']
|
dir: ['src/wasm-lib']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- uses: taiki-e/install-action@just
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@ -41,7 +42,7 @@ jobs:
|
|||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: |
|
run: |
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo clippy --all --tests --benches -- -D warnings
|
just lint
|
||||||
# If this fails, run "cargo check" to update Cargo.lock,
|
# If this fails, run "cargo check" to update Cargo.lock,
|
||||||
# then add Cargo.lock to the PR.
|
# then add Cargo.lock to the PR.
|
||||||
- name: Check Cargo.lock doesn't need updating
|
- name: Check Cargo.lock doesn't need updating
|
||||||
|
19
README.md
@ -351,25 +351,6 @@ PS: for the debug panel, the following JSON is useful for snapping the camera
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Tauri e2e tests
|
|
||||||
|
|
||||||
#### Windows (local only until the CI edge version mismatch is fixed)
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn install
|
|
||||||
yarn build:wasm-dev
|
|
||||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
|
||||||
yarn vite build --mode development
|
|
||||||
yarn tauri build --debug -b
|
|
||||||
$env:KITTYCAD_API_TOKEN="<YOUR_KITTYCAD_API_TOKEN>"
|
|
||||||
$env:VITE_KC_API_BASE_URL="https://api.dev.zoo.dev"
|
|
||||||
$env:E2E_TAURI_ENABLED="true"
|
|
||||||
$env:TS_NODE_COMPILER_OPTIONS='{"module": "commonjs"}'
|
|
||||||
$env:E2E_APPLICATION=".\src-tauri\target\debug\Zoo Modeling App.exe"
|
|
||||||
Stop-Process -Name msedgedriver
|
|
||||||
yarn wdio run wdio.conf.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
## KCL
|
## KCL
|
||||||
|
|
||||||
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
||||||
|
@ -147713,7 +147713,8 @@
|
|||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
"// Loft a square and a triangle.\nconst squareSketch = startSketchOn('XY')\n |> startProfileAt([-100, 200], %)\n |> line([200, 0], %)\n |> line([0, -200], %)\n |> line([-200, 0], %)\n |> lineTo([profileStartX(%), profileStartY(%)], %)\n |> close(%)\n\nconst triangleSketch = startSketchOn(offsetPlane('XY', 75))\n |> startProfileAt([0, 125], %)\n |> line([-15, -30], %)\n |> line([30, 0], %)\n |> lineTo([profileStartX(%), profileStartY(%)], %)\n |> close(%)\n\nloft([squareSketch, triangleSketch])",
|
"// Loft a square and a triangle.\nconst squareSketch = startSketchOn('XY')\n |> startProfileAt([-100, 200], %)\n |> line([200, 0], %)\n |> line([0, -200], %)\n |> line([-200, 0], %)\n |> lineTo([profileStartX(%), profileStartY(%)], %)\n |> close(%)\n\nconst triangleSketch = startSketchOn(offsetPlane('XY', 75))\n |> startProfileAt([0, 125], %)\n |> line([-15, -30], %)\n |> line([30, 0], %)\n |> lineTo([profileStartX(%), profileStartY(%)], %)\n |> close(%)\n\nloft([squareSketch, triangleSketch])",
|
||||||
"// Loft a square, a circle, and another circle.\nconst squareSketch = startSketchOn('XY')\n |> startProfileAt([-100, 200], %)\n |> line([200, 0], %)\n |> line([0, -200], %)\n |> line([-200, 0], %)\n |> lineTo([profileStartX(%), profileStartY(%)], %)\n |> close(%)\n\nconst circleSketch0 = startSketchOn(offsetPlane('XY', 75))\n |> circle([0, 100], 50, %)\n\nconst circleSketch1 = startSketchOn(offsetPlane('XY', 150))\n |> circle([0, 100], 20, %)\n\nloft([\n squareSketch,\n circleSketch0,\n circleSketch1\n])"
|
"// Loft a square, a circle, and another circle.\nconst squareSketch = startSketchOn('XY')\n |> startProfileAt([-100, 200], %)\n |> line([200, 0], %)\n |> line([0, -200], %)\n |> line([-200, 0], %)\n |> lineTo([profileStartX(%), profileStartY(%)], %)\n |> close(%)\n\nconst circleSketch0 = startSketchOn(offsetPlane('XY', 75))\n |> circle([0, 100], 50, %)\n\nconst circleSketch1 = startSketchOn(offsetPlane('XY', 150))\n |> circle([0, 100], 20, %)\n\nloft([\n squareSketch,\n circleSketch0,\n circleSketch1\n])",
|
||||||
|
"// Loft a square, a circle, and another circle with options.\nconst squareSketch = startSketchOn('XY')\n |> startProfileAt([-100, 200], %)\n |> line([200, 0], %)\n |> line([0, -200], %)\n |> line([-200, 0], %)\n |> lineTo([profileStartX(%), profileStartY(%)], %)\n |> close(%)\n\nconst circleSketch0 = startSketchOn(offsetPlane('XY', 75))\n |> circle([0, 100], 50, %)\n\nconst circleSketch1 = startSketchOn(offsetPlane('XY', 150))\n |> circle([0, 100], 20, %)\n\nloft([\n squareSketch,\n circleSketch0,\n circleSketch1\n], {\n // This can be set to override the automatically determined\n // topological base curve, which is usually the first section encountered.\n baseCurveIndex: 0,\n // Attempt to approximate rational curves (such as arcs) using a bezier.\n // This will remove banding around interpolations between arcs and non-arcs.\n // It may produce errors in other scenarios Over time, this field won't be necessary.\n bezApproximateRational: false,\n // Tolerance for the loft operation.\n tolerance: 0.000001,\n // Degree of the interpolation. Must be greater than zero.\n // For example, use 2 for quadratic, or 3 for cubic interpolation in\n // the V direction. This defaults to 2, if not specified.\n vDegree: 2\n})"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -112,7 +112,8 @@ test.describe('when using the file tree to', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
panesOpen,
|
openKclCodePanel,
|
||||||
|
openFilePanel,
|
||||||
createAndSelectProject,
|
createAndSelectProject,
|
||||||
pasteCodeInEditor,
|
pasteCodeInEditor,
|
||||||
createNewFileAndSelect,
|
createNewFileAndSelect,
|
||||||
@ -124,9 +125,9 @@ test.describe('when using the file tree to', () => {
|
|||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
await panesOpen(['files', 'code'])
|
|
||||||
|
|
||||||
await createAndSelectProject('project-000')
|
await createAndSelectProject('project-000')
|
||||||
|
await openKclCodePanel()
|
||||||
|
await openFilePanel()
|
||||||
// File the main.kcl with contents
|
// File the main.kcl with contents
|
||||||
const kclCube = await fsp.readFile(
|
const kclCube = await fsp.readFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||||
@ -201,4 +202,78 @@ test.describe('when using the file tree to', () => {
|
|||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'loading small file, then large, then back to small',
|
||||||
|
{
|
||||||
|
tag: '@electron',
|
||||||
|
},
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
panesOpen,
|
||||||
|
createAndSelectProject,
|
||||||
|
pasteCodeInEditor,
|
||||||
|
createNewFile,
|
||||||
|
openDebugPanel,
|
||||||
|
closeDebugPanel,
|
||||||
|
expectCmdLog,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await panesOpen(['files', 'code'])
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
|
||||||
|
// Create a small file
|
||||||
|
const kclCube = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
// pasted into main.kcl
|
||||||
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
|
// Create a large lego file
|
||||||
|
await createNewFile('lego')
|
||||||
|
const legoFile = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: 'lego.kcl' }),
|
||||||
|
})
|
||||||
|
await expect(legoFile).toBeVisible({ timeout: 60_000 })
|
||||||
|
await legoFile.click()
|
||||||
|
const kclLego = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/lego.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclLego)
|
||||||
|
const mainFile = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: 'main.kcl' }),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Open settings and enable the debug panel
|
||||||
|
await page
|
||||||
|
.getByRole('link', {
|
||||||
|
name: 'settings Settings',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||||
|
await page.getByTestId('settings-close-button').click()
|
||||||
|
|
||||||
|
await test.step('swap between small and large files', async () => {
|
||||||
|
await openDebugPanel()
|
||||||
|
// Previously created a file so we need to start back at main.kcl
|
||||||
|
await mainFile.click()
|
||||||
|
await expectCmdLog('[data-message-type="execution-done"]', 60_000)
|
||||||
|
// Click the large file
|
||||||
|
await legoFile.click()
|
||||||
|
// Once it is building, click back to the smaller file
|
||||||
|
await mainFile.click()
|
||||||
|
await expectCmdLog('[data-message-type="execution-done"]', 60_000)
|
||||||
|
await closeDebugPanel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -548,13 +548,16 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
|
|
||||||
createNewFileAndSelect: async (name: string) => {
|
createNewFileAndSelect: async (name: string) => {
|
||||||
return test?.step(`Create a file named ${name}, select it`, async () => {
|
return test?.step(`Create a file named ${name}, select it`, async () => {
|
||||||
|
await openFilePanel(page)
|
||||||
await page.getByTestId('create-file-button').click()
|
await page.getByTestId('create-file-button').click()
|
||||||
await page.getByTestId('file-rename-field').fill(name)
|
await page.getByTestId('file-rename-field').fill(name)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page
|
const newFile = page
|
||||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
.filter({ hasText: name })
|
.filter({ hasText: name })
|
||||||
.click()
|
|
||||||
|
await expect(newFile).toBeVisible()
|
||||||
|
await newFile.click()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -585,6 +588,15 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Sorry I don't have time to fix this right now, but runs like
|
||||||
|
* the one linked below show me that setting the open panes in this manner is not reliable.
|
||||||
|
* You can either set `openPanes` as a part of the same initScript we run in setupElectron/setup,
|
||||||
|
* or you can imperatively open the panes with functions like {openKclCodePanel}
|
||||||
|
* (or we can make a general openPane function that takes a paneId).,
|
||||||
|
* but having a separate initScript does not seem to work reliably.
|
||||||
|
* @link https://github.com/KittyCAD/modeling-app/actions/runs/10731890169/job/29762700806?pr=3807#step:20:19553
|
||||||
|
*/
|
||||||
panesOpen: async (paneIds: PaneId[]) => {
|
panesOpen: async (paneIds: PaneId[]) => {
|
||||||
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
|
@ -288,7 +288,7 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Refresh the application and see project setting applied', async () => {
|
await test.step('Refresh the application and see project setting applied', async () => {
|
||||||
await page.reload()
|
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
||||||
await settingsCloseButton.click()
|
await settingsCloseButton.click()
|
||||||
@ -364,47 +364,48 @@ test.describe('Testing settings', () => {
|
|||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async () => {},
|
folderSetupFn: async (dir) => {
|
||||||
|
const bracketDir = join(dir, 'project-000')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cube.kcl'),
|
||||||
|
join(bracketDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
join(bracketDir, '2.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8')
|
||||||
|
const kclCylinder = await fsp.readFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
panesOpen,
|
openKclCodePanel,
|
||||||
createAndSelectProject,
|
openFilePanel,
|
||||||
pasteCodeInEditor,
|
waitForPageLoad,
|
||||||
clickPane,
|
selectFile,
|
||||||
createNewFileAndSelect,
|
|
||||||
editorTextMatches,
|
editorTextMatches,
|
||||||
} = await getUtils(page, test)
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
await panesOpen([])
|
await test.step('Precondition: Open to second project file', async () => {
|
||||||
|
|
||||||
await test.step('Precondition: No projects exist', async () => {
|
|
||||||
await expect(page.getByTestId('home-section')).toBeVisible()
|
await expect(page.getByTestId('home-section')).toBeVisible()
|
||||||
const projectLinksPre = page.getByTestId('project-link')
|
await page.getByText('project-000').click()
|
||||||
await expect(projectLinksPre).toHaveCount(0)
|
await waitForPageLoad()
|
||||||
|
await openKclCodePanel()
|
||||||
|
await openFilePanel()
|
||||||
|
await editorTextMatches(kclCube)
|
||||||
|
|
||||||
|
await selectFile('2.kcl')
|
||||||
|
await editorTextMatches(kclCylinder)
|
||||||
})
|
})
|
||||||
|
|
||||||
await createAndSelectProject('project-000')
|
|
||||||
|
|
||||||
await clickPane('code')
|
|
||||||
const kclCube = await fsp.readFile(
|
|
||||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
await pasteCodeInEditor(kclCube)
|
|
||||||
|
|
||||||
await clickPane('files')
|
|
||||||
await createNewFileAndSelect('2.kcl')
|
|
||||||
|
|
||||||
const kclCylinder = await fsp.readFile(
|
|
||||||
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
await pasteCodeInEditor(kclCylinder)
|
|
||||||
|
|
||||||
const settingsOpenButton = page.getByRole('link', {
|
const settingsOpenButton = page.getByRole('link', {
|
||||||
name: 'settings Settings',
|
name: 'settings Settings',
|
||||||
})
|
})
|
||||||
@ -412,6 +413,9 @@ test.describe('Testing settings', () => {
|
|||||||
|
|
||||||
await test.step('Open and close settings', async () => {
|
await test.step('Open and close settings', async () => {
|
||||||
await settingsOpenButton.click()
|
await settingsOpenButton.click()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||||
|
).toBeVisible()
|
||||||
await settingsCloseButton.click()
|
await settingsCloseButton.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -534,7 +534,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
// Ensure the final toast remains.
|
// Ensure the final toast remains.
|
||||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||||
await expect(page.getByText(`a 2x8 lego`)).not.toBeVisible()
|
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
|
||||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||||
|
|
||||||
// Ensure you can copy the code for the final model.
|
// Ensure you can copy the code for the final model.
|
||||||
@ -690,40 +690,53 @@ test(
|
|||||||
'Text-to-CAD functionality',
|
'Text-to-CAD functionality',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
|
const projectName = 'project-000'
|
||||||
|
const prompt = 'lego 2x4'
|
||||||
|
const textToCadFileName = 'lego-2x4.kcl'
|
||||||
|
|
||||||
const { electronApp, page, dir } = await setupElectron({ testInfo })
|
const { electronApp, page, dir } = await setupElectron({ testInfo })
|
||||||
const fileExists = () =>
|
const fileExists = () =>
|
||||||
fs.existsSync(join(dir, 'project-000', 'lego-2x4.kcl'))
|
fs.existsSync(join(dir, projectName, textToCadFileName))
|
||||||
|
|
||||||
const { createAndSelectProject, panesOpen } = await getUtils(page, test)
|
const {
|
||||||
|
createAndSelectProject,
|
||||||
|
openFilePanel,
|
||||||
|
openKclCodePanel,
|
||||||
|
waitForPageLoad,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await panesOpen(['code', 'files'])
|
// Locators
|
||||||
|
const projectMenuButton = page.getByRole('button', { name: projectName })
|
||||||
|
const textToCadFileButton = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: textToCadFileName }),
|
||||||
|
})
|
||||||
|
const textToCadComment = page.getByText(
|
||||||
|
`// Generated by Text-to-CAD: ${prompt}`
|
||||||
|
)
|
||||||
|
|
||||||
// Create and navigate to the project
|
// Create and navigate to the project
|
||||||
await createAndSelectProject('project-000')
|
await createAndSelectProject('project-000')
|
||||||
|
|
||||||
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
|
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
|
||||||
await expect(
|
await waitForPageLoad()
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
await openFilePanel()
|
||||||
).toBeEnabled({
|
await openKclCodePanel()
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Test file creation`, async () => {
|
await test.step(`Test file creation`, async () => {
|
||||||
await sendPromptFromCommandBar(page, 'lego 2x4')
|
await sendPromptFromCommandBar(page, prompt)
|
||||||
// File is considered created if it shows up in the Project Files pane
|
// File is considered created if it shows up in the Project Files pane
|
||||||
const file = page.getByRole('button', { name: 'lego-2x4.kcl' })
|
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
|
||||||
await expect(file).toBeVisible({ timeout: 20_000 })
|
|
||||||
expect(fileExists()).toBeTruthy()
|
expect(fileExists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Test file navigation`, async () => {
|
await test.step(`Test file navigation`, async () => {
|
||||||
const file = page.getByRole('button', { name: 'lego-2x4.kcl' })
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
await file.click()
|
await textToCadFileButton.click()
|
||||||
const kclComment = page.getByText('Lego 2x4 Brick')
|
|
||||||
// File can be navigated and loaded assuming a specific KCL comment is loaded into the KCL code pane
|
// File can be navigated and loaded assuming a specific KCL comment is loaded into the KCL code pane
|
||||||
await expect(kclComment).toBeVisible({ timeout: 20_000 })
|
await expect(textToCadComment).toBeVisible({ timeout: 20_000 })
|
||||||
|
await expect(projectMenuButton).toContainText(textToCadFileName)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Test file deletion on rejection`, async () => {
|
await test.step(`Test file deletion on rejection`, async () => {
|
||||||
@ -737,6 +750,8 @@ test(
|
|||||||
)
|
)
|
||||||
await expect(submittingToastMessage).toBeVisible()
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
expect(fileExists()).toBeFalsy()
|
expect(fileExists()).toBeFalsy()
|
||||||
|
// Confirm we've navigated back to the main.kcl file after deletion
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
|
@ -79,5 +79,5 @@ linux:
|
|||||||
|
|
||||||
publish:
|
publish:
|
||||||
- provider: generic
|
- provider: generic
|
||||||
url: https://dl.zoo.dev/releases/modeling-app/test/electron-builder
|
url: https://dl.zoo.dev/releases/modeling-app
|
||||||
channel: latest
|
channel: latest
|
||||||
|
1
interface.d.ts
vendored
@ -30,7 +30,6 @@ export interface IElectronAPI {
|
|||||||
join: typeof path.join
|
join: typeof path.join
|
||||||
sep: typeof path.sep
|
sep: typeof path.sep
|
||||||
rename: (prev: string, next: string) => typeof fs.rename
|
rename: (prev: string, next: string) => typeof fs.rename
|
||||||
setBaseUrl: (value: string) => void
|
|
||||||
packageJson: {
|
packageJson: {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zoo-modeling-app",
|
"name": "zoo-modeling-app",
|
||||||
"version": "0.25.0",
|
"version": "0.25.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"author": {
|
"author": {
|
||||||
@ -137,7 +137,6 @@
|
|||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@lezer/generator": "^1.7.1",
|
"@lezer/generator": "^1.7.1",
|
||||||
"@playwright/test": "^1.46.1",
|
"@playwright/test": "^1.46.1",
|
||||||
"@tauri-apps/cli": "^2.0.0-rc.9",
|
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^15.0.2",
|
"@testing-library/react": "^15.0.2",
|
||||||
"@types/d3-force": "^3.0.10",
|
"@types/d3-force": "^3.0.10",
|
||||||
|
10
src/App.tsx
@ -122,11 +122,11 @@ export function App() {
|
|||||||
// Override the electron window draggable region behavior as well
|
// Override the electron window draggable region behavior as well
|
||||||
// when the button is down in the stream
|
// when the button is down in the stream
|
||||||
style={
|
style={
|
||||||
{
|
isDesktop() && context.store?.buttonDownInStream
|
||||||
'-webkit-app-region': context.store?.buttonDownInStream
|
? ({
|
||||||
? 'no-drag'
|
'-webkit-app-region': 'no-drag',
|
||||||
: '',
|
} as React.CSSProperties)
|
||||||
} as React.CSSProperties
|
: {}
|
||||||
}
|
}
|
||||||
project={{ project, file }}
|
project={{ project, file }}
|
||||||
enableMenu={true}
|
enableMenu={true}
|
||||||
|
@ -20,6 +20,8 @@ import {
|
|||||||
ToolbarItemResolved,
|
ToolbarItemResolved,
|
||||||
ToolbarModeName,
|
ToolbarModeName,
|
||||||
} from 'lib/toolbar'
|
} from 'lib/toolbar'
|
||||||
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
|
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
|
|
||||||
export function Toolbar({
|
export function Toolbar({
|
||||||
className = '',
|
className = '',
|
||||||
@ -288,6 +290,11 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
|
|||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
inert={false}
|
inert={false}
|
||||||
|
wrapperStyle={
|
||||||
|
isDesktop()
|
||||||
|
? ({ '-webkit-app-region': 'no-drag' } as React.CSSProperties)
|
||||||
|
: {}
|
||||||
|
}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
wrapperClassName="!p-4 !pointer-events-auto"
|
wrapperClassName="!p-4 !pointer-events-auto"
|
||||||
contentClassName="!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch"
|
contentClassName="!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch"
|
||||||
@ -337,6 +344,7 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
|
|||||||
<li key={link.label} className="contents">
|
<li key={link.label} className="contents">
|
||||||
<a
|
<a
|
||||||
href={link.url}
|
href={link.url}
|
||||||
|
onClick={openExternalBrowserIfDesktop(link.url)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="flex items-center rounded-sm p-1 no-underline text-inherit hover:bg-primary/10 hover:text-primary dark:hover:bg-chalkboard-70 dark:hover:text-inherit"
|
className="flex items-center rounded-sm p-1 no-underline text-inherit hover:bg-primary/10 hover:text-primary dark:hover:bg-chalkboard-70 dark:hover:text-inherit"
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
grid-template-columns: 1fr auto 1fr;
|
grid-template-columns: 1fr auto 1fr;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header.desktopApp {
|
||||||
/* Make the header act as a handle to drag the electron app window,
|
/* Make the header act as a handle to drag the electron app window,
|
||||||
* per the electron docs: https://www.electronjs.org/docs/latest/tutorial/window-customization#set-custom-draggable-region
|
* per the electron docs: https://www.electronjs.org/docs/latest/tutorial/window-customization#set-custom-draggable-region
|
||||||
* all interactive elements opt-out of this behavior by default in src/index.css
|
* all interactive elements opt-out of this behavior by default in src/index.css
|
||||||
|
@ -6,6 +6,7 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
import { RefreshButton } from 'components/RefreshButton'
|
import { RefreshButton } from 'components/RefreshButton'
|
||||||
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
||||||
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
|
|
||||||
interface AppHeaderProps extends React.PropsWithChildren {
|
interface AppHeaderProps extends React.PropsWithChildren {
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
@ -32,7 +33,9 @@ export const AppHeader = ({
|
|||||||
className={
|
className={
|
||||||
'w-full grid ' +
|
'w-full grid ' +
|
||||||
styles.header +
|
styles.header +
|
||||||
' overlaid-panes sticky top-0 z-20 px-2 items-start ' +
|
` ${
|
||||||
|
isDesktop() ? styles.desktopApp + ' ' : ''
|
||||||
|
}overlaid-panes sticky top-0 z-20 px-2 items-start ` +
|
||||||
className
|
className
|
||||||
}
|
}
|
||||||
style={style}
|
style={style}
|
||||||
|
@ -12,6 +12,7 @@ interface TooltipProps extends React.PropsWithChildren {
|
|||||||
position?: TooltipPosition
|
position?: TooltipPosition
|
||||||
wrapperClassName?: string
|
wrapperClassName?: string
|
||||||
contentClassName?: string
|
contentClassName?: string
|
||||||
|
wrapperStyle?: React.CSSProperties
|
||||||
delay?: number
|
delay?: number
|
||||||
hoverOnly?: boolean
|
hoverOnly?: boolean
|
||||||
inert?: boolean
|
inert?: boolean
|
||||||
@ -22,6 +23,7 @@ export default function Tooltip({
|
|||||||
position = 'top',
|
position = 'top',
|
||||||
wrapperClassName: className,
|
wrapperClassName: className,
|
||||||
contentClassName,
|
contentClassName,
|
||||||
|
wrapperStyle = {},
|
||||||
delay = 200,
|
delay = 200,
|
||||||
hoverOnly = false,
|
hoverOnly = false,
|
||||||
inert = true,
|
inert = true,
|
||||||
@ -36,7 +38,10 @@ export default function Tooltip({
|
|||||||
} ${styles.tooltipWrapper} ${hoverOnly ? '' : styles.withFocus} ${
|
} ${styles.tooltipWrapper} ${hoverOnly ? '' : styles.withFocus} ${
|
||||||
styles[position]
|
styles[position]
|
||||||
} ${className}`}
|
} ${className}`}
|
||||||
style={{ '--_delay': delay + 'ms' } as React.CSSProperties}
|
style={Object.assign(
|
||||||
|
{ '--_delay': delay + 'ms' } as React.CSSProperties,
|
||||||
|
wrapperStyle
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className={`rounded ${styles.tooltip} ${contentClassName || ''}`}>
|
<div className={`rounded ${styles.tooltip} ${contentClassName || ''}`}>
|
||||||
{children}
|
{children}
|
||||||
|
@ -8,7 +8,7 @@ import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
|||||||
import { isNodeSafeToReplace } from 'lang/queryAst'
|
import { isNodeSafeToReplace } from 'lang/queryAst'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useModelingContext } from './useModelingContext'
|
import { useModelingContext } from './useModelingContext'
|
||||||
import { PathToNode, SourceRange, parse, recast } from 'lang/wasm'
|
import { PathToNode, SourceRange } from 'lang/wasm'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
|
|
||||||
export const getVarNameModal = createSetVarNameModal(SetVarNameModal)
|
export const getVarNameModal = createSetVarNameModal(SetVarNameModal)
|
||||||
@ -23,8 +23,7 @@ export function useConvertToVariable(range?: SourceRange) {
|
|||||||
}, [enable])
|
}, [enable])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const parsed = parse(recast(ast))
|
const parsed = ast
|
||||||
if (trap(parsed)) return
|
|
||||||
|
|
||||||
const meta = isNodeSafeToReplace(
|
const meta = isNodeSafeToReplace(
|
||||||
parsed,
|
parsed,
|
||||||
|
@ -50,6 +50,14 @@ body.dark {
|
|||||||
@apply text-chalkboard-10;
|
@apply text-chalkboard-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body,
|
||||||
|
.body-bg,
|
||||||
|
.dark .body-bg {
|
||||||
|
@apply bg-chalkboard-100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@apply bg-chalkboard-20;
|
@apply bg-chalkboard-20;
|
||||||
}
|
}
|
||||||
@ -287,32 +295,11 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
/* Modified from the very helpful https://www.transition.style/#in:circle:hesitate */
|
/*
|
||||||
@keyframes circle-in-hesitate {
|
This is where your own custom Tailwind utility classes can go,
|
||||||
0% {
|
which lets you use them with @apply in your CSS, and get
|
||||||
clip-path: circle(
|
autocomplete in classNames in your JSX.
|
||||||
var(--circle-size-start, 0%) at var(--circle-x, 50%)
|
*/
|
||||||
var(--circle-y, 50%)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
clip-path: circle(
|
|
||||||
var(--circle-size-mid, 40%) at var(--circle-x, 50%) var(--circle-y, 50%)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
clip-path: circle(
|
|
||||||
var(--circle-size-end, 125%) at var(--circle-x, 50%)
|
|
||||||
var(--circle-y, 50%)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.in-circle-hesitate {
|
|
||||||
animation: var(--circle-duration, 2.5s)
|
|
||||||
var(--circle-timing, cubic-bezier(0.25, 1, 0.3, 1)) circle-in-hesitate
|
|
||||||
both;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#code-mirror-override .cm-scroller,
|
#code-mirror-override .cm-scroller,
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
parseProjectSettings,
|
parseProjectSettings,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
DEFAULT_HOST,
|
|
||||||
PROJECT_ENTRYPOINT,
|
PROJECT_ENTRYPOINT,
|
||||||
PROJECT_FOLDER,
|
PROJECT_FOLDER,
|
||||||
PROJECT_SETTINGS_FILE_NAME,
|
PROJECT_SETTINGS_FILE_NAME,
|
||||||
@ -556,28 +555,6 @@ export const getUser = async (
|
|||||||
token: string,
|
token: string,
|
||||||
hostname: string
|
hostname: string
|
||||||
): Promise<Models['User_type']> => {
|
): Promise<Models['User_type']> => {
|
||||||
// Use the host passed in if it's set.
|
|
||||||
// Otherwise, use the default host.
|
|
||||||
const host = !hostname ? DEFAULT_HOST : hostname
|
|
||||||
|
|
||||||
// Change the baseURL to the one we want.
|
|
||||||
let baseurl = host
|
|
||||||
if (!(host.indexOf('http://') === 0) && !(host.indexOf('https://') === 0)) {
|
|
||||||
baseurl = `https://${host}`
|
|
||||||
if (host.indexOf('localhost') === 0) {
|
|
||||||
baseurl = `http://${host}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use kittycad library to fetch the user info from /user/me
|
|
||||||
if (baseurl !== DEFAULT_HOST) {
|
|
||||||
// The TypeScript generated library uses environment variables for this
|
|
||||||
// because it was intended for NodeJS.
|
|
||||||
// Needs to stay like this because window.electron.kittycad needs it
|
|
||||||
// internally.
|
|
||||||
window.electron.setBaseUrl(baseurl)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await window.electron.kittycad('users.get_user_self', {
|
const user = await window.electron.kittycad('users.get_user_self', {
|
||||||
client: { token },
|
client: { token },
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
kclManager,
|
kclManager,
|
||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
import { CallExpression, SourceRange, Expr, parse, recast } from 'lang/wasm'
|
import { CallExpression, SourceRange, Expr, parse } from 'lang/wasm'
|
||||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { EditorSelection, SelectionRange } from '@codemirror/state'
|
import { EditorSelection, SelectionRange } from '@codemirror/state'
|
||||||
@ -300,8 +300,7 @@ export function processCodeMirrorRanges({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||||
const updated = parse(recast(kclManager.ast))
|
const updated = kclManager.ast
|
||||||
if (err(updated)) return
|
|
||||||
|
|
||||||
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
|
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
|
||||||
if (
|
if (
|
||||||
|
@ -129,12 +129,16 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
id: 'loft',
|
id: 'loft',
|
||||||
onClick: () => console.error('Loft not yet implemented'),
|
onClick: () => console.error('Loft not yet implemented'),
|
||||||
icon: 'loft',
|
icon: 'loft',
|
||||||
status: 'unavailable',
|
status: 'kcl-only',
|
||||||
title: 'Loft',
|
title: 'Loft',
|
||||||
hotkey: 'L',
|
hotkey: 'L',
|
||||||
description:
|
description:
|
||||||
'Create a 3D body by blending between two or more sketches.',
|
'Create a 3D body by blending between two or more sketches.',
|
||||||
links: [
|
links: [
|
||||||
|
{
|
||||||
|
label: 'KCL docs',
|
||||||
|
url: 'https://zoo.dev/docs/kcl/loft',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'GitHub discussion',
|
label: 'GitHub discussion',
|
||||||
url: 'https://github.com/KittyCAD/modeling-app/discussions/613',
|
url: 'https://github.com/KittyCAD/modeling-app/discussions/613',
|
||||||
|
10
src/main.ts
@ -2,7 +2,14 @@
|
|||||||
// template that ElectronJS provides.
|
// template that ElectronJS provides.
|
||||||
|
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron'
|
import {
|
||||||
|
app,
|
||||||
|
BrowserWindow,
|
||||||
|
ipcMain,
|
||||||
|
dialog,
|
||||||
|
shell,
|
||||||
|
nativeTheme,
|
||||||
|
} from 'electron'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { Issuer } from 'openid-client'
|
import { Issuer } from 'openid-client'
|
||||||
import { Bonjour, Service } from 'bonjour-service'
|
import { Bonjour, Service } from 'bonjour-service'
|
||||||
@ -75,6 +82,7 @@ const createWindow = (filePath?: string): BrowserWindow => {
|
|||||||
icon: path.resolve(process.cwd(), 'assets', 'icon.png'),
|
icon: path.resolve(process.cwd(), 'assets', 'icon.png'),
|
||||||
frame: os.platform() !== 'darwin',
|
frame: os.platform() !== 'darwin',
|
||||||
titleBarStyle: 'hiddenInset',
|
titleBarStyle: 'hiddenInset',
|
||||||
|
backgroundColor: nativeTheme.shouldUseDarkColors ? '#1C1C1C' : '#FCFCFC',
|
||||||
})
|
})
|
||||||
|
|
||||||
// and load the index.html of the app.
|
// and load the index.html of the app.
|
||||||
|
@ -93,9 +93,6 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
isWindows,
|
isWindows,
|
||||||
isLinux,
|
isLinux,
|
||||||
},
|
},
|
||||||
// IMPORTANT NOTE: kittycad.ts reads process.env.BASE_URL. But there is
|
|
||||||
// no way to set it across the bridge boundary. We need to make it a command.
|
|
||||||
setBaseUrl: (value: string) => (process.env.BASE_URL = value),
|
|
||||||
process: {
|
process: {
|
||||||
// Setter/getter has to be created because
|
// Setter/getter has to be created because
|
||||||
// these are read-only over the boundary.
|
// these are read-only over the boundary.
|
||||||
|
@ -58,19 +58,23 @@ const SignIn = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="bg-primary h-screen grid place-items-stretch m-0 p-2">
|
<main
|
||||||
|
className="bg-primary h-screen grid place-items-stretch m-0 p-2"
|
||||||
|
style={
|
||||||
|
isDesktop()
|
||||||
|
? ({
|
||||||
|
'-webkit-app-region': 'drag',
|
||||||
|
} as CSSProperties)
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
style={
|
style={
|
||||||
{
|
isDesktop()
|
||||||
height: 'calc(100vh - 16px)',
|
? ({ '-webkit-app-region': 'no-drag' } as CSSProperties)
|
||||||
'--circle-x': '14%',
|
: {}
|
||||||
'--circle-y': '12%',
|
|
||||||
'--circle-size-mid': '15%',
|
|
||||||
'--circle-size-end': '200%',
|
|
||||||
'--circle-timing': 'cubic-bezier(0.25, 1, 0.4, 0.9)',
|
|
||||||
} as CSSProperties
|
|
||||||
}
|
}
|
||||||
className="in-circle-hesitate body-bg py-5 px-12 rounded-lg grid place-items-center overflow-y-auto"
|
className="body-bg py-5 px-12 rounded-lg grid place-items-center overflow-y-auto"
|
||||||
>
|
>
|
||||||
<div className="max-w-7xl grid gap-5 grid-cols-3 xl:grid-cols-4 xl:grid-rows-5">
|
<div className="max-w-7xl grid gap-5 grid-cols-3 xl:grid-cols-4 xl:grid-rows-5">
|
||||||
<div className="col-span-2 xl:col-span-3 xl:row-span-3 max-w-3xl mr-8 mb-8">
|
<div className="col-span-2 xl:col-span-3 xl:row-span-3 max-w-3xl mr-8 mb-8">
|
||||||
@ -194,7 +198,7 @@ const SignIn = () => {
|
|||||||
<div className="flex gap-4 flex-wrap items-center">
|
<div className="flex gap-4 flex-wrap items-center">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="externalLink"
|
Element="externalLink"
|
||||||
to="https://zoo.dev/docs/kcl-samples/ball-bearing"
|
to="https://zoo.dev/docs/kcl-samples/a-parametric-bearing-pillow-block"
|
||||||
iconStart={{ icon: 'settings' }}
|
iconStart={{ icon: 'settings' }}
|
||||||
className="border-chalkboard-30 dark:border-chalkboard-80"
|
className="border-chalkboard-30 dark:border-chalkboard-80"
|
||||||
>
|
>
|
||||||
|
33
src/wasm-lib/Cargo.lock
generated
@ -370,9 +370,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.16"
|
version = "4.5.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
|
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@ -380,9 +380,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.15"
|
version = "4.5.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
|
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
@ -620,9 +620,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dashmap"
|
name = "dashmap"
|
||||||
version = "6.0.1"
|
version = "6.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28"
|
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
@ -1345,7 +1345,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.13"
|
version = "0.2.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
@ -1357,7 +1357,7 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"criterion",
|
"criterion",
|
||||||
"dashmap 6.0.1",
|
"dashmap 6.1.0",
|
||||||
"databake",
|
"databake",
|
||||||
"derive-docs",
|
"derive-docs",
|
||||||
"expectorate",
|
"expectorate",
|
||||||
@ -1399,7 +1399,7 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"winnow 0.5.40",
|
"winnow",
|
||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2581,9 +2581,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.127"
|
version = "1.0.128"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.5",
|
"indexmap 2.2.5",
|
||||||
"itoa",
|
"itoa",
|
||||||
@ -3117,7 +3117,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"winnow 0.6.18",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3800,15 +3800,6 @@ version = "0.52.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winnow"
|
|
||||||
version = "0.5.40"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.18"
|
version = "0.6.18"
|
||||||
|
@ -15,7 +15,7 @@ data-encoding = "2.6.0"
|
|||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad.workspace = true
|
kittycad.workspace = true
|
||||||
serde_json = "1.0.127"
|
serde_json = "1.0.128"
|
||||||
tokio = { version = "1.40.0", features = ["sync"] }
|
tokio = { version = "1.40.0", features = ["sync"] }
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
||||||
|
@ -2,3 +2,6 @@
|
|||||||
new-test name:
|
new-test name:
|
||||||
echo "kcl_test!(\"{{name}}\", {{name}});" >> tests/executor/visuals.rs
|
echo "kcl_test!(\"{{name}}\", {{name}});" >> tests/executor/visuals.rs
|
||||||
TWENTY_TWENTY=overwrite cargo nextest run --test executor -E 'test(=visuals::{{name}})'
|
TWENTY_TWENTY=overwrite cargo nextest run --test executor -E 'test(=visuals::{{name}})'
|
||||||
|
|
||||||
|
lint:
|
||||||
|
cargo clippy --all --tests --benches -- -D warnings
|
||||||
|
@ -11,5 +11,5 @@ hyper = { version = "0.14.29", features = ["server"] }
|
|||||||
kcl-lib = { version = "0.2", path = "../kcl" }
|
kcl-lib = { version = "0.2", path = "../kcl" }
|
||||||
pico-args = "0.5.0"
|
pico-args = "0.5.0"
|
||||||
serde = { version = "1.0.209", features = ["derive"] }
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
serde_json = "1.0.127"
|
serde_json = "1.0.128"
|
||||||
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.2.13"
|
version = "0.2.14"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
@ -16,9 +16,9 @@ async-recursion = "1.1.1"
|
|||||||
async-trait = "0.1.82"
|
async-trait = "0.1.82"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.5.16", default-features = false, optional = true, features = ["std", "derive"] }
|
clap = { version = "4.5.17", default-features = false, optional = true, features = ["std", "derive"] }
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
dashmap = "6.0.1"
|
dashmap = "6.1.0"
|
||||||
databake = { version = "0.1.8", features = ["derive"] }
|
databake = { version = "0.1.8", features = ["derive"] }
|
||||||
derive-docs = { version = "0.1.26", path = "../derive-docs" }
|
derive-docs = { version = "0.1.26", path = "../derive-docs" }
|
||||||
form_urlencoded = "1.2.1"
|
form_urlencoded = "1.2.1"
|
||||||
@ -37,7 +37,7 @@ reqwest = { version = "0.11.26", default-features = false, features = ["stream",
|
|||||||
ropey = "1.6.1"
|
ropey = "1.6.1"
|
||||||
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"] }
|
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"] }
|
||||||
serde = { version = "1.0.209", features = ["derive"] }
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
serde_json = "1.0.127"
|
serde_json = "1.0.128"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
tabled = { version = "0.15.0", optional = true }
|
tabled = { version = "0.15.0", optional = true }
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.63"
|
||||||
@ -47,7 +47,7 @@ url = { version = "2.5.2", features = ["serde"] }
|
|||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
||||||
validator = { version = "0.18.1", features = ["derive"] }
|
validator = { version = "0.18.1", features = ["derive"] }
|
||||||
winnow = "0.5.40"
|
winnow = "0.6.18"
|
||||||
zip = { version = "2.0.0", default-features = false }
|
zip = { version = "2.0.0", default-features = false }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||||
use kcl_lib::test_server;
|
use kcl_lib::{settings::types::UnitLength::Mm, test_server};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
pub fn bench_execute(c: &mut Criterion) {
|
pub fn bench_execute(c: &mut Criterion) {
|
||||||
@ -13,26 +13,42 @@ pub fn bench_execute(c: &mut Criterion) {
|
|||||||
// Configure Criterion.rs to detect smaller differences and increase sample size to improve
|
// Configure Criterion.rs to detect smaller differences and increase sample size to improve
|
||||||
// precision and counteract the resulting noise.
|
// precision and counteract the resulting noise.
|
||||||
group.sample_size(10);
|
group.sample_size(10);
|
||||||
group.bench_with_input(BenchmarkId::new("execute_", name), &code, |b, &s| {
|
group.bench_with_input(BenchmarkId::new("execute", name), &code, |b, &s| {
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
|
|
||||||
// Spawn a future onto the runtime
|
// Spawn a future onto the runtime
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
rt.block_on(test_server::execute_and_snapshot(
|
rt.block_on(test_server::execute_and_snapshot(s, Mm)).unwrap();
|
||||||
s,
|
|
||||||
kcl_lib::settings::types::UnitLength::Mm,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
group.finish();
|
group.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, bench_execute);
|
pub fn bench_lego(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("executor_lego_pattern");
|
||||||
|
// Configure Criterion.rs to detect smaller differences and increase sample size to improve
|
||||||
|
// precision and counteract the resulting noise.
|
||||||
|
group.sample_size(10);
|
||||||
|
// Create lego bricks with N x 10 bumps, where N is each element of `sizes`.
|
||||||
|
let sizes = vec![1, 2, 4];
|
||||||
|
for size in sizes {
|
||||||
|
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||||
|
let rt = Runtime::new().unwrap();
|
||||||
|
let code = LEGO_PROGRAM.replace("{{N}}", &size.to_string());
|
||||||
|
// Spawn a future onto the runtime
|
||||||
|
b.iter(|| {
|
||||||
|
rt.block_on(test_server::execute_and_snapshot(&code, Mm)).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, bench_lego, bench_execute);
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
|
|
||||||
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
|
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
|
||||||
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
|
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
|
||||||
const SERVER_RACK_HEAVY_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-heavy.kcl");
|
const SERVER_RACK_HEAVY_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-heavy.kcl");
|
||||||
const SERVER_RACK_LITE_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-lite.kcl");
|
const SERVER_RACK_LITE_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-lite.kcl");
|
||||||
|
const LEGO_PROGRAM: &str = include_str!("../../tests/executor/inputs/slow_lego.kcl.tmpl");
|
||||||
|
@ -995,20 +995,20 @@ impl SketchSurface {
|
|||||||
}
|
}
|
||||||
pub(crate) fn x_axis(&self) -> Point3d {
|
pub(crate) fn x_axis(&self) -> Point3d {
|
||||||
match self {
|
match self {
|
||||||
SketchSurface::Plane(plane) => plane.x_axis.clone(),
|
SketchSurface::Plane(plane) => plane.x_axis,
|
||||||
SketchSurface::Face(face) => face.x_axis.clone(),
|
SketchSurface::Face(face) => face.x_axis,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn y_axis(&self) -> Point3d {
|
pub(crate) fn y_axis(&self) -> Point3d {
|
||||||
match self {
|
match self {
|
||||||
SketchSurface::Plane(plane) => plane.y_axis.clone(),
|
SketchSurface::Plane(plane) => plane.y_axis,
|
||||||
SketchSurface::Face(face) => face.y_axis.clone(),
|
SketchSurface::Face(face) => face.y_axis,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn z_axis(&self) -> Point3d {
|
pub(crate) fn z_axis(&self) -> Point3d {
|
||||||
match self {
|
match self {
|
||||||
SketchSurface::Plane(plane) => plane.z_axis.clone(),
|
SketchSurface::Plane(plane) => plane.z_axis,
|
||||||
SketchSurface::Face(face) => face.z_axis.clone(),
|
SketchSurface::Face(face) => face.z_axis,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1304,7 +1304,7 @@ impl Point2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema, Default)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct Point3d {
|
pub struct Point3d {
|
||||||
pub x: f64,
|
pub x: f64,
|
||||||
@ -1313,6 +1313,7 @@ pub struct Point3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Point3d {
|
impl Point3d {
|
||||||
|
pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 };
|
||||||
pub fn new(x: f64, y: f64, z: f64) -> Self {
|
pub fn new(x: f64, y: f64, z: f64) -> Self {
|
||||||
Self { x, y, z }
|
Self { x, y, z }
|
||||||
}
|
}
|
||||||
|
@ -927,7 +927,7 @@ pub fn function_body(i: TokenSlice) -> PResult<Program> {
|
|||||||
|
|
||||||
match body_items_within_function.parse_next(i) {
|
match body_items_within_function.parse_next(i) {
|
||||||
Err(ErrMode::Backtrack(_)) => {
|
Err(ErrMode::Backtrack(_)) => {
|
||||||
i.reset(start);
|
i.reset(&start);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
@ -937,7 +937,7 @@ pub fn function_body(i: TokenSlice) -> PResult<Program> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Err(ErrMode::Backtrack(_)), _) => {
|
(Err(ErrMode::Backtrack(_)), _) => {
|
||||||
i.reset(start);
|
i.reset(&start);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
(Err(e), _) => return Err(e),
|
(Err(e), _) => return Err(e),
|
||||||
@ -1276,7 +1276,7 @@ fn unary_expression(i: TokenSlice) -> PResult<UnaryExpression> {
|
|||||||
|
|
||||||
/// Consume tokens that make up a binary expression, but don't actually return them.
|
/// Consume tokens that make up a binary expression, but don't actually return them.
|
||||||
/// Why not?
|
/// Why not?
|
||||||
/// Because this is designed to be used with .recognize() within the `binary_expression` parser.
|
/// Because this is designed to be used with .take() within the `binary_expression` parser.
|
||||||
fn binary_expression_tokens(i: TokenSlice) -> PResult<Vec<BinaryExpressionToken>> {
|
fn binary_expression_tokens(i: TokenSlice) -> PResult<Vec<BinaryExpressionToken>> {
|
||||||
let first = operand.parse_next(i).map(BinaryExpressionToken::from)?;
|
let first = operand.parse_next(i).map(BinaryExpressionToken::from)?;
|
||||||
let remaining: Vec<_> = repeat(
|
let remaining: Vec<_> = repeat(
|
||||||
@ -1308,7 +1308,7 @@ fn binary_expression(i: TokenSlice) -> PResult<BinaryExpression> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn binary_expr_in_parens(i: TokenSlice) -> PResult<BinaryExpression> {
|
fn binary_expr_in_parens(i: TokenSlice) -> PResult<BinaryExpression> {
|
||||||
let span_with_brackets = bracketed_section.recognize().parse_next(i)?;
|
let span_with_brackets = bracketed_section.take().parse_next(i)?;
|
||||||
let n = span_with_brackets.len();
|
let n = span_with_brackets.len();
|
||||||
let mut span_no_brackets = &span_with_brackets[1..n - 1];
|
let mut span_no_brackets = &span_with_brackets[1..n - 1];
|
||||||
let expr = binary_expression.parse_next(&mut span_no_brackets)?;
|
let expr = binary_expression.parse_next(&mut span_no_brackets)?;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use winnow::{
|
use winnow::{
|
||||||
error::{ErrorKind, ParseError, StrContext},
|
error::{ErrorKind, ParseError, StrContext},
|
||||||
|
stream::Stream,
|
||||||
Located,
|
Located,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,14 +103,17 @@ impl<C> std::default::Default for ContextError<C> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, C> winnow::error::ParserError<I> for ContextError<C> {
|
impl<I, C> winnow::error::ParserError<I> for ContextError<C>
|
||||||
|
where
|
||||||
|
I: Stream,
|
||||||
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_error_kind(_input: &I, _kind: ErrorKind) -> Self {
|
fn from_error_kind(_input: &I, _kind: ErrorKind) -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn append(self, _input: &I, _kind: ErrorKind) -> Self {
|
fn append(self, _input: &I, _input_checkpoint: &<I as Stream>::Checkpoint, _kind: ErrorKind) -> Self {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,9 +123,12 @@ impl<I, C> winnow::error::ParserError<I> for ContextError<C> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, I> winnow::error::AddContext<I, C> for ContextError<C> {
|
impl<C, I> winnow::error::AddContext<I, C> for ContextError<C>
|
||||||
|
where
|
||||||
|
I: Stream,
|
||||||
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn add_context(mut self, _input: &I, ctx: C) -> Self {
|
fn add_context(mut self, _input: &I, _input_checkpoint: &<I as Stream>::Checkpoint, ctx: C) -> Self {
|
||||||
self.context.push(ctx);
|
self.context.push(ctx);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
//! Functions related to extruding.
|
//! Functions related to extruding.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use derive_docs::stdlib;
|
use derive_docs::stdlib;
|
||||||
|
use kittycad::types::{ExtrusionFaceCapType, ExtrusionFaceInfo};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -90,7 +93,7 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
|||||||
adjust_camera: false,
|
adjust_camera: false,
|
||||||
planar_normal: if let SketchSurface::Plane(plane) = &sketch_group.on {
|
planar_normal: if let SketchSurface::Plane(plane) = &sketch_group.on {
|
||||||
// We pass in the normal for the plane here.
|
// We pass in the normal for the plane here.
|
||||||
Some(plane.z_axis.clone().into())
|
Some(plane.z_axis.into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
@ -98,7 +101,7 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
args.send_modeling_cmd(
|
args.batch_modeling_cmd(
|
||||||
id,
|
id,
|
||||||
kittycad::types::ModelingCmd::Extrude {
|
kittycad::types::ModelingCmd::Extrude {
|
||||||
target: sketch_group.id,
|
target: sketch_group.id,
|
||||||
@ -111,7 +114,7 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
|||||||
// Disable the sketch mode.
|
// Disable the sketch mode.
|
||||||
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
|
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
|
||||||
.await?;
|
.await?;
|
||||||
extrude_groups.push(do_post_extrude(sketch_group.clone(), length, id, args.clone()).await?);
|
extrude_groups.push(do_post_extrude(sketch_group.clone(), length, args.clone()).await?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(extrude_groups.into())
|
Ok(extrude_groups.into())
|
||||||
@ -120,7 +123,6 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
|||||||
pub(crate) async fn do_post_extrude(
|
pub(crate) async fn do_post_extrude(
|
||||||
sketch_group: SketchGroup,
|
sketch_group: SketchGroup,
|
||||||
length: f64,
|
length: f64,
|
||||||
id: Uuid,
|
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<ExtrudeGroup>, KclError> {
|
) -> Result<Box<ExtrudeGroup>, KclError> {
|
||||||
// Bring the object to the front of the scene.
|
// Bring the object to the front of the scene.
|
||||||
@ -164,7 +166,7 @@ pub(crate) async fn do_post_extrude(
|
|||||||
|
|
||||||
let solid3d_info = args
|
let solid3d_info = args
|
||||||
.send_modeling_cmd(
|
.send_modeling_cmd(
|
||||||
id,
|
uuid::Uuid::new_v4(),
|
||||||
kittycad::types::ModelingCmd::Solid3DGetExtrusionFaceInfo {
|
kittycad::types::ModelingCmd::Solid3DGetExtrusionFaceInfo {
|
||||||
edge_id,
|
edge_id,
|
||||||
object_id: sketch_group.id,
|
object_id: sketch_group.id,
|
||||||
@ -181,91 +183,95 @@ pub(crate) async fn do_post_extrude(
|
|||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
|
||||||
for face_info in face_infos.iter() {
|
for (curve_id, face_id) in face_infos
|
||||||
if face_info.cap == kittycad::types::ExtrusionFaceCapType::None {
|
.iter()
|
||||||
|
.filter(|face_info| face_info.cap == ExtrusionFaceCapType::None)
|
||||||
|
.filter_map(|face_info| {
|
||||||
if let (Some(curve_id), Some(face_id)) = (face_info.curve_id, face_info.face_id) {
|
if let (Some(curve_id), Some(face_id)) = (face_info.curve_id, face_info.face_id) {
|
||||||
args.batch_modeling_cmd(
|
Some((curve_id, face_id))
|
||||||
uuid::Uuid::new_v4(),
|
} else {
|
||||||
kittycad::types::ModelingCmd::Solid3DGetOppositeEdge {
|
None
|
||||||
edge_id: curve_id,
|
|
||||||
object_id: sketch_group.id,
|
|
||||||
face_id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
args.batch_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
kittycad::types::ModelingCmd::Solid3DGetPrevAdjacentEdge {
|
|
||||||
edge_id: curve_id,
|
|
||||||
object_id: sketch_group.id,
|
|
||||||
face_id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
{
|
||||||
|
// Batch these commands, because the Rust code doesn't actually care about the outcome.
|
||||||
// Create a hashmap for quick id lookup
|
// So, there's no need to await them.
|
||||||
let mut face_id_map = std::collections::HashMap::new();
|
// Instead, the Typescript codebases (which handles WebSocket sends when compiled via Wasm)
|
||||||
// creating fake ids for start and end caps is to make extrudes mock-execute safe
|
// uses this to build the artifact graph, which the UI needs.
|
||||||
let mut start_cap_id = if args.ctx.is_mock { Some(Uuid::new_v4()) } else { None };
|
args.batch_modeling_cmd(
|
||||||
let mut end_cap_id = if args.ctx.is_mock { Some(Uuid::new_v4()) } else { None };
|
uuid::Uuid::new_v4(),
|
||||||
|
kittycad::types::ModelingCmd::Solid3DGetOppositeEdge {
|
||||||
for face_info in face_infos {
|
edge_id: curve_id,
|
||||||
match face_info.cap {
|
object_id: sketch_group.id,
|
||||||
kittycad::types::ExtrusionFaceCapType::Bottom => start_cap_id = face_info.face_id,
|
face_id,
|
||||||
kittycad::types::ExtrusionFaceCapType::Top => end_cap_id = face_info.face_id,
|
},
|
||||||
_ => {
|
)
|
||||||
if let Some(curve_id) = face_info.curve_id {
|
.await?;
|
||||||
face_id_map.insert(curve_id, face_info.face_id);
|
|
||||||
}
|
args.batch_modeling_cmd(
|
||||||
}
|
uuid::Uuid::new_v4(),
|
||||||
}
|
kittycad::types::ModelingCmd::Solid3DGetPrevAdjacentEdge {
|
||||||
|
edge_id: curve_id,
|
||||||
|
object_id: sketch_group.id,
|
||||||
|
face_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Faces {
|
||||||
|
sides: face_id_map,
|
||||||
|
start_cap_id,
|
||||||
|
end_cap_id,
|
||||||
|
} = analyze_faces(&args, face_infos);
|
||||||
// Iterate over the sketch_group.value array and add face_id to GeoMeta
|
// Iterate over the sketch_group.value array and add face_id to GeoMeta
|
||||||
let mut new_value: Vec<ExtrudeSurface> = Vec::new();
|
let new_value = sketch_group
|
||||||
for path in sketch_group.value.iter() {
|
.value
|
||||||
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
|
.iter()
|
||||||
match path {
|
.flat_map(|path| {
|
||||||
Path::TangentialArc { .. } | Path::TangentialArcTo { .. } => {
|
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
|
||||||
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::ExtrudeArc {
|
match path {
|
||||||
face_id: *actual_face_id,
|
Path::TangentialArc { .. } | Path::TangentialArcTo { .. } => {
|
||||||
tag: path.get_base().tag.clone(),
|
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::ExtrudeArc {
|
||||||
geo_meta: GeoMeta {
|
face_id: *actual_face_id,
|
||||||
id: path.get_base().geo_meta.id,
|
tag: path.get_base().tag.clone(),
|
||||||
metadata: path.get_base().geo_meta.metadata.clone(),
|
geo_meta: GeoMeta {
|
||||||
},
|
id: path.get_base().geo_meta.id,
|
||||||
});
|
metadata: path.get_base().geo_meta.metadata.clone(),
|
||||||
new_value.push(extrude_surface);
|
},
|
||||||
}
|
});
|
||||||
Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } => {
|
Some(extrude_surface)
|
||||||
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
}
|
||||||
face_id: *actual_face_id,
|
Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } => {
|
||||||
tag: path.get_base().tag.clone(),
|
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
||||||
geo_meta: GeoMeta {
|
face_id: *actual_face_id,
|
||||||
id: path.get_base().geo_meta.id,
|
tag: path.get_base().tag.clone(),
|
||||||
metadata: path.get_base().geo_meta.metadata.clone(),
|
geo_meta: GeoMeta {
|
||||||
},
|
id: path.get_base().geo_meta.id,
|
||||||
});
|
metadata: path.get_base().geo_meta.metadata.clone(),
|
||||||
new_value.push(extrude_surface);
|
},
|
||||||
|
});
|
||||||
|
Some(extrude_surface)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if args.ctx.is_mock {
|
||||||
|
// Only pre-populate the extrude surface if we are in mock mode.
|
||||||
|
|
||||||
|
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
||||||
|
// pushing this values with a fake face_id to make extrudes mock-execute safe
|
||||||
|
face_id: Uuid::new_v4(),
|
||||||
|
tag: path.get_base().tag.clone(),
|
||||||
|
geo_meta: GeoMeta {
|
||||||
|
id: path.get_base().geo_meta.id,
|
||||||
|
metadata: path.get_base().geo_meta.metadata.clone(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Some(extrude_surface)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
} else if args.ctx.is_mock {
|
})
|
||||||
// Only pre-populate the extrude surface if we are in mock mode.
|
.collect();
|
||||||
new_value.push(ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
|
||||||
// pushing this values with a fake face_id to make extrudes mock-execute safe
|
|
||||||
face_id: Uuid::new_v4(),
|
|
||||||
tag: path.get_base().tag.clone(),
|
|
||||||
geo_meta: GeoMeta {
|
|
||||||
id: path.get_base().geo_meta.id,
|
|
||||||
metadata: path.get_base().geo_meta.metadata.clone(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Box::new(ExtrudeGroup {
|
Ok(Box::new(ExtrudeGroup {
|
||||||
// Ok so you would think that the id would be the id of the extrude group,
|
// Ok so you would think that the id would be the id of the extrude group,
|
||||||
@ -273,11 +279,45 @@ pub(crate) async fn do_post_extrude(
|
|||||||
// sketch group.
|
// sketch group.
|
||||||
id: sketch_group.id,
|
id: sketch_group.id,
|
||||||
value: new_value,
|
value: new_value,
|
||||||
sketch_group: sketch_group.clone(),
|
meta: sketch_group.meta.clone(),
|
||||||
|
sketch_group,
|
||||||
height: length,
|
height: length,
|
||||||
start_cap_id,
|
start_cap_id,
|
||||||
end_cap_id,
|
end_cap_id,
|
||||||
edge_cuts: vec![],
|
edge_cuts: vec![],
|
||||||
meta: sketch_group.meta,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Faces {
|
||||||
|
/// Maps curve ID to face ID for each side.
|
||||||
|
sides: HashMap<Uuid, Option<Uuid>>,
|
||||||
|
/// Top face ID.
|
||||||
|
end_cap_id: Option<Uuid>,
|
||||||
|
/// Bottom face ID.
|
||||||
|
start_cap_id: Option<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn analyze_faces(args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
|
||||||
|
let mut faces = Faces {
|
||||||
|
sides: HashMap::with_capacity(face_infos.len()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
if args.ctx.is_mock {
|
||||||
|
// Create fake IDs for start and end caps, to make extrudes mock-execute safe
|
||||||
|
faces.start_cap_id = Some(Uuid::new_v4());
|
||||||
|
faces.end_cap_id = Some(Uuid::new_v4());
|
||||||
|
}
|
||||||
|
for face_info in face_infos {
|
||||||
|
match face_info.cap {
|
||||||
|
ExtrusionFaceCapType::Bottom => faces.start_cap_id = face_info.face_id,
|
||||||
|
ExtrusionFaceCapType::Top => faces.end_cap_id = face_info.face_id,
|
||||||
|
ExtrusionFaceCapType::None => {
|
||||||
|
if let Some(curve_id) = face_info.curve_id {
|
||||||
|
faces.sides.insert(curve_id, face_info.face_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
faces
|
||||||
|
}
|
||||||
|
@ -12,7 +12,7 @@ use crate::{
|
|||||||
std::{extrude::do_post_extrude, fillet::default_tolerance, Args},
|
std::{extrude::do_post_extrude, fillet::default_tolerance, Args},
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_V_DEGREE: u32 = 1;
|
const DEFAULT_V_DEGREE: u32 = 2;
|
||||||
|
|
||||||
/// Data for a loft.
|
/// Data for a loft.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
@ -98,6 +98,39 @@ pub async fn loft(args: Args) -> Result<KclValue, KclError> {
|
|||||||
///
|
///
|
||||||
/// loft([squareSketch, circleSketch0, circleSketch1])
|
/// loft([squareSketch, circleSketch0, circleSketch1])
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// // Loft a square, a circle, and another circle with options.
|
||||||
|
/// const squareSketch = startSketchOn('XY')
|
||||||
|
/// |> startProfileAt([-100, 200], %)
|
||||||
|
/// |> line([200, 0], %)
|
||||||
|
/// |> line([0, -200], %)
|
||||||
|
/// |> line([-200, 0], %)
|
||||||
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
/// |> close(%)
|
||||||
|
///
|
||||||
|
/// const circleSketch0 = startSketchOn(offsetPlane('XY', 75))
|
||||||
|
/// |> circle([0, 100], 50, %)
|
||||||
|
///
|
||||||
|
/// const circleSketch1 = startSketchOn(offsetPlane('XY', 150))
|
||||||
|
/// |> circle([0, 100], 20, %)
|
||||||
|
///
|
||||||
|
/// loft([squareSketch, circleSketch0, circleSketch1], {
|
||||||
|
/// // This can be set to override the automatically determined
|
||||||
|
/// // topological base curve, which is usually the first section encountered.
|
||||||
|
/// baseCurveIndex: 0,
|
||||||
|
/// // Attempt to approximate rational curves (such as arcs) using a bezier.
|
||||||
|
/// // This will remove banding around interpolations between arcs and non-arcs.
|
||||||
|
/// // It may produce errors in other scenarios Over time, this field won't be necessary.
|
||||||
|
/// bezApproximateRational: false,
|
||||||
|
/// // Tolerance for the loft operation.
|
||||||
|
/// tolerance: 0.000001,
|
||||||
|
/// // Degree of the interpolation. Must be greater than zero.
|
||||||
|
/// // For example, use 2 for quadratic, or 3 for cubic interpolation in
|
||||||
|
/// // the V direction. This defaults to 2, if not specified.
|
||||||
|
/// vDegree: 2,
|
||||||
|
/// })
|
||||||
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "loft",
|
name = "loft",
|
||||||
}]
|
}]
|
||||||
@ -137,5 +170,5 @@ async fn inner_loft(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Using the first sketch as the base curve, idk we might want to change this later.
|
// Using the first sketch as the base curve, idk we might want to change this later.
|
||||||
do_post_extrude(sketch_groups[0].clone(), 0.0, id, args).await
|
do_post_extrude(sketch_groups[0].clone(), 0.0, args).await
|
||||||
}
|
}
|
||||||
|
@ -488,7 +488,7 @@ layout: manual
|
|||||||
buf.push_str(&fn_docs);
|
buf.push_str(&fn_docs);
|
||||||
|
|
||||||
// Write the file.
|
// Write the file.
|
||||||
expectorate::assert_contents(&format!("../../../docs/kcl/{}.md", internal_fn.name()), &buf);
|
expectorate::assert_contents(format!("../../../docs/kcl/{}.md", internal_fn.name()), &buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,7 +299,7 @@ async fn inner_revolve(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do_post_extrude(sketch_group, 0.0, id, args).await
|
do_post_extrude(sketch_group, 0.0, args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1269,7 +1269,7 @@ pub(crate) async fn inner_start_profile_at(
|
|||||||
adjust_camera: false,
|
adjust_camera: false,
|
||||||
planar_normal: if let SketchSurface::Plane(plane) = &sketch_surface {
|
planar_normal: if let SketchSurface::Plane(plane) = &sketch_surface {
|
||||||
// We pass in the normal for the plane here.
|
// We pass in the normal for the plane here.
|
||||||
Some(plane.z_axis.clone().into())
|
Some(plane.z_axis.into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
@ -50,13 +50,13 @@ pub fn token(i: &mut Located<&str>) -> PResult<Token> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn block_comment(i: &mut Located<&str>) -> PResult<Token> {
|
fn block_comment(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
let inner = ("/*", take_until(0.., "*/"), "*/").recognize();
|
let inner = ("/*", take_until(0.., "*/"), "*/").take();
|
||||||
let (value, range) = inner.with_span().parse_next(i)?;
|
let (value, range) = inner.with_span().parse_next(i)?;
|
||||||
Ok(Token::from_range(range, TokenType::BlockComment, value.to_string()))
|
Ok(Token::from_range(range, TokenType::BlockComment, value.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_comment(i: &mut Located<&str>) -> PResult<Token> {
|
fn line_comment(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
let inner = (r#"//"#, take_till(0.., ['\n', '\r'])).recognize();
|
let inner = (r#"//"#, take_till(0.., ['\n', '\r'])).take();
|
||||||
let (value, range) = inner.with_span().parse_next(i)?;
|
let (value, range) = inner.with_span().parse_next(i)?;
|
||||||
Ok(Token::from_range(range, TokenType::LineComment, value.to_string()))
|
Ok(Token::from_range(range, TokenType::LineComment, value.to_string()))
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ fn number(i: &mut Located<&str>) -> PResult<Token> {
|
|||||||
// No digits before the decimal point.
|
// No digits before the decimal point.
|
||||||
('.', digit1).map(|_| ()),
|
('.', digit1).map(|_| ()),
|
||||||
));
|
));
|
||||||
let (value, range) = number_parser.recognize().with_span().parse_next(i)?;
|
let (value, range) = number_parser.take().with_span().parse_next(i)?;
|
||||||
Ok(Token::from_range(range, TokenType::Number, value.to_string()))
|
Ok(Token::from_range(range, TokenType::Number, value.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,12 +79,12 @@ fn whitespace(i: &mut Located<&str>) -> PResult<Token> {
|
|||||||
|
|
||||||
fn inner_word(i: &mut Located<&str>) -> PResult<()> {
|
fn inner_word(i: &mut Located<&str>) -> PResult<()> {
|
||||||
one_of(('a'..='z', 'A'..='Z', '_')).parse_next(i)?;
|
one_of(('a'..='z', 'A'..='Z', '_')).parse_next(i)?;
|
||||||
repeat(0.., one_of(('a'..='z', 'A'..='Z', '0'..='9', '_'))).parse_next(i)?;
|
repeat::<_, _, (), _, _>(0.., one_of(('a'..='z', 'A'..='Z', '0'..='9', '_'))).parse_next(i)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn word(i: &mut Located<&str>) -> PResult<Token> {
|
fn word(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
let (value, range) = inner_word.recognize().with_span().parse_next(i)?;
|
let (value, range) = inner_word.take().with_span().parse_next(i)?;
|
||||||
Ok(Token::from_range(range, TokenType::Word, value.to_string()))
|
Ok(Token::from_range(range, TokenType::Word, value.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,9 +162,9 @@ fn inner_single_quote(i: &mut Located<&str>) -> PResult<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn string(i: &mut Located<&str>) -> PResult<Token> {
|
fn string(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
let single_quoted_string = ('\'', inner_single_quote.recognize(), '\'');
|
let single_quoted_string = ('\'', inner_single_quote.take(), '\'');
|
||||||
let double_quoted_string = ('"', inner_double_quote.recognize(), '"');
|
let double_quoted_string = ('"', inner_double_quote.take(), '"');
|
||||||
let either_quoted_string = alt((single_quoted_string.recognize(), double_quoted_string.recognize()));
|
let either_quoted_string = alt((single_quoted_string.take(), double_quoted_string.take()));
|
||||||
let (value, range): (&str, _) = either_quoted_string.with_span().parse_next(i)?;
|
let (value, range): (&str, _) = either_quoted_string.with_span().parse_next(i)?;
|
||||||
Ok(Token::from_range(range, TokenType::String, value.to_string()))
|
Ok(Token::from_range(range, TokenType::String, value.to_string()))
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 135 KiB |
BIN
src/wasm-lib/kcl/tests/outputs/serial_test_example_loft2.png
Normal file
After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 65 KiB |
@ -1,3 +1,3 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.80.1"
|
channel = "1.81.0"
|
||||||
components = ["clippy", "rustfmt"]
|
components = ["clippy", "rustfmt"]
|
||||||
|
81
src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// 2x8 Lego Brick
|
||||||
|
// A standard Lego brick with 2 bumps wide and 8 bumps long.
|
||||||
|
// Define constants
|
||||||
|
const lbumps = 10 // number of bumps long
|
||||||
|
const wbumps = {{N}} // number of bumps wide
|
||||||
|
const pitch = 8.0
|
||||||
|
const clearance = 0.1
|
||||||
|
const bumpDiam = 4.8
|
||||||
|
const bumpHeight = 1.8
|
||||||
|
const height = 9.6
|
||||||
|
const t = (pitch - (2 * clearance) - bumpDiam) / 2.0
|
||||||
|
const totalLength = lbumps * pitch - (2.0 * clearance)
|
||||||
|
const totalWidth = wbumps * pitch - (2.0 * clearance)
|
||||||
|
// Create the plane for the pegs. This is a hack so that the pegs can be patterned along the face of the lego base.
|
||||||
|
const pegFace = {
|
||||||
|
plane: {
|
||||||
|
origin: { x: 0, y: 0, z: height },
|
||||||
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
|
zAxis: { x: 0, y: 0, z: 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create the plane for the tubes underneath the lego. This is a hack so that the tubes can be patterned underneath the lego.
|
||||||
|
const tubeFace = {
|
||||||
|
plane: {
|
||||||
|
origin: { x: 0, y: 0, z: height - t },
|
||||||
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
|
zAxis: { x: 0, y: 0, z: 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Make the base
|
||||||
|
const s = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-totalWidth / 2, -totalLength / 2], %)
|
||||||
|
|> line([totalWidth, 0], %)
|
||||||
|
|> line([0, totalLength], %)
|
||||||
|
|> line([-totalWidth, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(height, %)
|
||||||
|
|
||||||
|
// Sketch and extrude a rectangular shape to create the shell underneath the lego. This is a hack until we have a shell function.
|
||||||
|
const shellExtrude = startSketchOn(s, "start")
|
||||||
|
|> startProfileAt([
|
||||||
|
-(totalWidth / 2 - t),
|
||||||
|
-(totalLength / 2 - t)
|
||||||
|
], %)
|
||||||
|
|> line([totalWidth - (2 * t), 0], %)
|
||||||
|
|> line([0, totalLength - (2 * t)], %)
|
||||||
|
|> line([-(totalWidth - (2 * t)), 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(-(height - t), %)
|
||||||
|
|
||||||
|
fn tr = (i) => {
|
||||||
|
let j = i + 1
|
||||||
|
let x = (j/wbumps) * pitch
|
||||||
|
let y = (j % wbumps) * pitch
|
||||||
|
return {
|
||||||
|
translate: [x, y, 0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the pegs on the top of the base
|
||||||
|
const totalBumps = (wbumps * lbumps)-1
|
||||||
|
const peg = startSketchOn(s, 'end')
|
||||||
|
|> circle([
|
||||||
|
-(pitch*(wbumps-1)/2),
|
||||||
|
-(pitch*(lbumps-1)/2)
|
||||||
|
], bumpDiam / 2, %)
|
||||||
|
|> patternLinear2d({
|
||||||
|
axis: [1, 0],
|
||||||
|
repetitions: wbumps-1,
|
||||||
|
distance: pitch
|
||||||
|
}, %)
|
||||||
|
|> patternLinear2d({
|
||||||
|
axis: [0, 1],
|
||||||
|
repetitions: lbumps-1,
|
||||||
|
distance: pitch
|
||||||
|
}, %)
|
||||||
|
|> extrude(bumpHeight, %)
|
||||||
|
// |> patternTransform(int(totalBumps-1), tr, %)
|
||||||
|
|
66
yarn.lock
@ -2353,72 +2353,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.8.4.tgz#0ff84b6a0e4b394335cf7ccf759c36b58cbd02eb"
|
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.8.4.tgz#0ff84b6a0e4b394335cf7ccf759c36b58cbd02eb"
|
||||||
integrity sha512-iO5Ujgw3O1yIxWDe9FgUPNkGjyT657b1WNX52u+Wv1DyBFEpdCdGkuVaky0M3hHFqNWjAmHWTn4wgj9rTr7ZQg==
|
integrity sha512-iO5Ujgw3O1yIxWDe9FgUPNkGjyT657b1WNX52u+Wv1DyBFEpdCdGkuVaky0M3hHFqNWjAmHWTn4wgj9rTr7ZQg==
|
||||||
|
|
||||||
"@tauri-apps/cli-darwin-arm64@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-rc.9.tgz#d6d9522b549a73ffb2c10ee273e6ac766dfa5914"
|
|
||||||
integrity sha512-RaCx1KpMX27iS1Cn7MYbVA0Gc5NsjU0Z1Qo42ibzF4OHInOkDcx3qjAaE+xD572Lb9ksBO725cIcYCdgqGu4Vw==
|
|
||||||
|
|
||||||
"@tauri-apps/cli-darwin-x64@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-rc.9.tgz#7ae9abfbeff998f13608d9248bdadba73b1560c0"
|
|
||||||
integrity sha512-KKUs8kbHYZrcmY/AjKjxEEm7aHGWQsn3+BGsgamKl97k2K5R5Z0KLJUy6QVhUSISEIievjDPmBDIwgA6mlrCLQ==
|
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-rc.9.tgz#8330576565f9ac411011d491a26e94d9116eb5ad"
|
|
||||||
integrity sha512-OgVCt72g0AnIB3zuKJLEIOCNeviiNeLoQQsVs7ESaqxZ/gMXY35yGVhrFm83eAQ0G4BervHDog15bsY3Dxbc/g==
|
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm64-gnu@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-rc.9.tgz#9b4b79dd256c39fed495fd8b7ffdb798078c61ab"
|
|
||||||
integrity sha512-7kQcXXXpCYB0AWbTRaKAim3JVMKdrxVOiqnOW+7elkqDQxDqmLQho2ah1qHv7LzZ6Z83u5QejrRLeHrrdo3PEg==
|
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm64-musl@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-rc.9.tgz#5afd06c1601ff823b7d82785236f63af379fd6d4"
|
|
||||||
integrity sha512-2hqANZrydqZpptUsfAHSL5DIaEfHN73UGEu+5keFCV1Irh+QPydr1CYrqhgFF982ev6Ars7nxALwpPhEODjYlg==
|
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-x64-gnu@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-rc.9.tgz#39185adc857e3e8474008600b7f0a6e0e42abdbf"
|
|
||||||
integrity sha512-Zjna6eoVSlmZtzAXgH27sgJRnczNzMKRiGsMpY00PFxN9sbQwlsS3yMfB8GHsBeBoq+qJQsteRwhrn1mj6e3Rg==
|
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-x64-musl@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-rc.9.tgz#a8d703010892622cf38e87950f5d2920833fac88"
|
|
||||||
integrity sha512-8ODcbvwZw29sAWns36BeBYJ3iu3Mtv4J3WkcoVbanVCP8nu7ja3401VnWBjckRiI1iDJIm59m6ojVkGYQhAe9Q==
|
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-arm64-msvc@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-rc.9.tgz#8ddea7d990b701357fe3dfd8e8e1783898206d85"
|
|
||||||
integrity sha512-j6jJId8hlid/W4ezDRNK49DSjxb82W6d1qVqO7zksKdZLy8tVzFkZXwEeKhabzRQsO87KL34I+ciRlmInGis0Q==
|
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-ia32-msvc@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-rc.9.tgz#ffa340d2dbf0e87355fa92650fbd707adc12d84e"
|
|
||||||
integrity sha512-w9utY58kfzJS+iLCjyQyQbJS8YaCM8YCWkgK2ZkySmHAdnqdGeyJEWig1qrLH1TWd+O6K3TlCNv55ujeAtOE4w==
|
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-x64-msvc@2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-rc.9.tgz#93f0cdc8c6999227aeee86741b553c16cb7ac20f"
|
|
||||||
integrity sha512-+l2RcpTthzYkw3VsmcZkb099Jfl0d21a9VIFxdk+duKeYieRpb0MsIBP6fS7WlNAeqrinC0zi/zt+Nia6mPuyw==
|
|
||||||
|
|
||||||
"@tauri-apps/cli@^2.0.0-rc.9":
|
|
||||||
version "2.0.0-rc.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-2.0.0-rc.9.tgz#b641ad224dd055aae4f101c14d0696d2e06862c0"
|
|
||||||
integrity sha512-cjj5HVKHUlxL87TN7ZZpnlMgcBS+ToIyfLB6jpaNDZ9Op0/qzccWGZpPbW2P/BnfF/qwHzVJNUPGANFyvBSUeg==
|
|
||||||
optionalDependencies:
|
|
||||||
"@tauri-apps/cli-darwin-arm64" "2.0.0-rc.9"
|
|
||||||
"@tauri-apps/cli-darwin-x64" "2.0.0-rc.9"
|
|
||||||
"@tauri-apps/cli-linux-arm-gnueabihf" "2.0.0-rc.9"
|
|
||||||
"@tauri-apps/cli-linux-arm64-gnu" "2.0.0-rc.9"
|
|
||||||
"@tauri-apps/cli-linux-arm64-musl" "2.0.0-rc.9"
|
|
||||||
"@tauri-apps/cli-linux-x64-gnu" "2.0.0-rc.9"
|
|
||||||
"@tauri-apps/cli-linux-x64-musl" "2.0.0-rc.9"
|
|
||||||
"@tauri-apps/cli-win32-arm64-msvc" "2.0.0-rc.9"
|
|
||||||
"@tauri-apps/cli-win32-ia32-msvc" "2.0.0-rc.9"
|
|
||||||
"@tauri-apps/cli-win32-x64-msvc" "2.0.0-rc.9"
|
|
||||||
|
|
||||||
"@testing-library/dom@^10.0.0":
|
"@testing-library/dom@^10.0.0":
|
||||||
version "10.4.0"
|
version "10.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8"
|
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8"
|
||||||
|