Compare commits
26 Commits
kurt-test-
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
49c8fc4c97 | |||
3cbda10eab | |||
0f3432b5a0 | |||
f11dc07f0b | |||
e49beb6609 | |||
b8f27b77a8 | |||
fa7e31223d | |||
f04c4588df | |||
c95812efa6 | |||
96385cd5ee | |||
64707edaad | |||
27baf135e7 | |||
a4cf68c661 | |||
403e074249 | |||
50259aa052 | |||
1739f3dafe | |||
7ceb518446 | |||
36a6b8c0ea | |||
bbdca7421e | |||
03c6f6d60e | |||
18c7e7934a | |||
bf650fd129 | |||
81ccb65f15 | |||
335b5100ae | |||
1162ff3b03 | |||
5e8227ead8 |
394
.github/workflows/build-test-publish-apps.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: build-test-publish-apps
|
||||
name: build-publish-apps
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@ -21,7 +21,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
prepare-json-files:
|
||||
prepare-files:
|
||||
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||
outputs:
|
||||
version: ${{ steps.export_version.outputs.version }}
|
||||
@ -33,6 +33,19 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
# TODO: see if we can fetch from main instead if no diff at src/wasm-lib
|
||||
- name: Run build:wasm
|
||||
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
|
||||
|
||||
- name: Set nightly version
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
@ -42,36 +55,50 @@ jobs:
|
||||
# TODO: see if we ned to add updater test URL here https://dl.zoo.dev/releases/modeling-app/updater-test/last_update.json
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ github.event_name == 'schedule' || env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
name: prepared-files
|
||||
path: |
|
||||
package.json
|
||||
src/wasm-lib/pkg/wasm_lib*
|
||||
|
||||
- id: export_version
|
||||
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
build-test-app-macos:
|
||||
needs: [prepare-json-files]
|
||||
runs-on: macos-14
|
||||
build-apps:
|
||||
needs: [prepare-files]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-14, windows-2022, ubuntu-22.04]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
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_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: github.event_name == 'schedule'
|
||||
name: prepared-files
|
||||
|
||||
- name: Copy updated .json files
|
||||
if: github.event_name == 'schedule'
|
||||
- name: Copy prepared files
|
||||
run: |
|
||||
ls -l artifact
|
||||
cp artifact/package.json package.json
|
||||
ls -R prepared-files
|
||||
cp prepared-files/package.json package.json
|
||||
cp prepared-files/src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
mkdir src/wasm-lib/pkg
|
||||
cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
uses: actions/setup-node@v4
|
||||
@ -81,79 +108,10 @@ jobs:
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- name: Run build:wasm
|
||||
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
|
||||
|
||||
# TODO: sign the app (and updater bundle potentially)
|
||||
- name: Add signing certificate
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
run: chmod +x add-osx-cert.sh && ./add-osx-cert.sh
|
||||
|
||||
- name: Build the app for arm64
|
||||
run: "yarn electron-forge make"
|
||||
|
||||
- name: Build the app for x64
|
||||
run: "yarn electron-forge make --arch x64"
|
||||
|
||||
- name: List artifacts
|
||||
run: "ls -R out/make"
|
||||
|
||||
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: "out/make/*/*/*/*"
|
||||
|
||||
|
||||
build-test-app-windows:
|
||||
needs: [prepare-json-files]
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
|
||||
- name: Copy updated .json files
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
ls -l artifact
|
||||
cp artifact/package.json package.json
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- name: Run build:wasm manually
|
||||
shell: bash
|
||||
env:
|
||||
MODE: ${{ env.BUILD_RELEASE == 'true' && '--release' || '--debug' }}
|
||||
run: |
|
||||
mkdir src/wasm-lib/pkg; cd src/wasm-lib
|
||||
echo "building with ${{ env.MODE }}"
|
||||
npx wasm-pack build --target web --out-dir pkg ${{ env.MODE }}
|
||||
cd ../../
|
||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
- run: yarn tronb:vite
|
||||
|
||||
- name: Prepare certificate and variables (Windows only)
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }}
|
||||
run: |
|
||||
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
|
||||
cat /d/Certificate_pkcs12.p12
|
||||
@ -168,7 +126,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Setup certicate with SSM KSP (Windows only)
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }}
|
||||
run: |
|
||||
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
|
||||
msiexec /i smtools-windows-x64.msi /quiet /qn
|
||||
@ -178,83 +136,47 @@ jobs:
|
||||
smksp_cert_sync.exe
|
||||
shell: cmd
|
||||
|
||||
- name: Build the app for x64
|
||||
run: "yarn electron-forge make --arch x64"
|
||||
- name: Build the app
|
||||
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
|
||||
|
||||
- name: Build the app for arm64
|
||||
run: "yarn electron-forge make --arch arm64"
|
||||
- name: List artifacts in out/
|
||||
run: ls -R out
|
||||
|
||||
- name: List artifacts
|
||||
run: "ls -R out/make"
|
||||
|
||||
- name: Sign using Signtool
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
env:
|
||||
THUMBPRINT: "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D"
|
||||
X64_FILE: "D:\\a\\modeling-app\\modeling-app\\out\\make\\squirrel.windows\\x64\\Zoo Modeling App-*Setup.exe"
|
||||
ARM64_FILE: "D:\\a\\modeling-app\\modeling-app\\out\\make\\squirrel.windows\\arm64\\Zoo Modeling App-*Setup.exe"
|
||||
- name: Prepare the tauri update bundles (macOS)
|
||||
if: ${{ env.BUILD_RELEASE && matrix.os == 'macos-14' }}
|
||||
run: |
|
||||
signtool.exe sign /sha1 ${{ env.THUMBPRINT }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "${{ env.X64_FILE }}"
|
||||
signtool.exe verify /v /pa "${{ env.X64_FILE }}"
|
||||
signtool.exe sign /sha1 ${{ env.THUMBPRINT }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "${{ env.ARM64_FILE }}"
|
||||
signtool.exe verify /v /pa "${{ env.ARM64_FILE }}"
|
||||
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
|
||||
with:
|
||||
path: "out/make/*/*/*"
|
||||
|
||||
# TODO: Run e2e tests
|
||||
|
||||
|
||||
build-test-app-ubuntu:
|
||||
needs: [prepare-json-files]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: github.event_name == 'schedule'
|
||||
|
||||
- name: Copy updated .json files
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
ls -l artifact
|
||||
cp artifact/package.json package.json
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- name: Run build:wasm
|
||||
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
|
||||
|
||||
- name: Build the app for arm64
|
||||
run: "yarn electron-forge make --arch arm64"
|
||||
|
||||
- name: Build the app for x64
|
||||
run: "yarn electron-forge make --arch x64"
|
||||
|
||||
- name: List artifacts
|
||||
run: "ls -R out/make"
|
||||
name: out-${{ matrix.os }}
|
||||
path: |
|
||||
out/Zoo*.*
|
||||
out/latest*.yml
|
||||
out/tauri
|
||||
|
||||
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
|
||||
|
||||
# TODO: sign the app (and updater bundle potentially)
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: "out/make/*/*/*"
|
||||
# TODO: add the updater tests back
|
||||
|
||||
|
||||
publish-apps-release:
|
||||
@ -262,87 +184,107 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
|
||||
needs: [prepare-json-files, build-test-app-macos, build-test-app-windows, build-test-app-ubuntu]
|
||||
needs: [prepare-files, build-apps]
|
||||
env:
|
||||
VERSION_NO_V: ${{ needs.prepare-json-files.outputs.version }}
|
||||
VERSION: ${{ github.event_name == 'release' && format('v{0}', needs.prepare-json-files.outputs.version) || needs.prepare-json-files.outputs.version }}
|
||||
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
||||
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }}
|
||||
BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }}
|
||||
WEBSITE_DIR: ${{ github.event_name == 'release' && 'dl.zoo.dev/releases/modeling-app' || 'dl.zoo.dev/releases/modeling-app/nightly' }}
|
||||
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' }}
|
||||
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' }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Generate the update static endpoint
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-windows-2022
|
||||
path: out
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-macos-14
|
||||
path: out
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-ubuntu-22.04
|
||||
path: out
|
||||
|
||||
- name: Generate the download static endpoint
|
||||
run: |
|
||||
ls -l artifact/*/*oo*
|
||||
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
||||
WINDOWS_X86_64_SIG=`cat artifact/msi/*x64*.msi.zip.sig`
|
||||
WINDOWS_AARCH64_SIG=`cat artifact/msi/*arm64*.msi.zip.sig`
|
||||
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
||||
RELEASE_DIR=https://${WEBSITE_DIR}
|
||||
jq --null-input \
|
||||
--arg version "${VERSION}" \
|
||||
--arg pub_date "${PUB_DATE}" \
|
||||
--arg notes "${NOTES}" \
|
||||
--arg darwin_sig "$DARWIN_SIG" \
|
||||
--arg darwin_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}.app.tar.gz" \
|
||||
--arg windows_x86_64_sig "$WINDOWS_X86_64_SIG" \
|
||||
--arg windows_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
||||
--arg windows_aarch64_sig "$WINDOWS_AARCH64_SIG" \
|
||||
--arg windows_aarch64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_arm64_en-US.msi.zip" \
|
||||
--arg mac_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-mac.dmg" \
|
||||
--arg mac_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-mac.dmg" \
|
||||
--arg windows_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-win.msi" \
|
||||
--arg windows_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-win.msi" \
|
||||
'{
|
||||
"version": $version,
|
||||
"pub_date": $pub_date,
|
||||
"notes": $notes,
|
||||
"platforms": {
|
||||
"dmg-arm64": {
|
||||
"url": $mac_arm64_url
|
||||
},
|
||||
"dmg-x64": {
|
||||
"url": $mac_x64_url
|
||||
},
|
||||
"msi-arm64": {
|
||||
"url": $windows_arm64_url
|
||||
},
|
||||
"msi-x64": {
|
||||
"url": $windows_x64_url
|
||||
}
|
||||
}
|
||||
}' > 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": $darwin_sig,
|
||||
"url": $darwin_url
|
||||
"signature": $mac_x64_sig,
|
||||
"url": $mac_x64_url
|
||||
},
|
||||
"darwin-aarch64": {
|
||||
"signature": $darwin_sig,
|
||||
"url": $darwin_url
|
||||
"signature": $mac_arm64_sig,
|
||||
"url": $mac_arm64_url
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"signature": $windows_x86_64_sig,
|
||||
"url": $windows_x86_64_url
|
||||
},
|
||||
"windows-aarch64": {
|
||||
"signature": $windows_aarch64_sig,
|
||||
"url": $windows_aarch64_url
|
||||
"signature": $windows_sig,
|
||||
"url": $windows_url
|
||||
}
|
||||
}
|
||||
}' > last_update.json
|
||||
cat last_update.json
|
||||
|
||||
- name: Generate the download static endpoint
|
||||
run: |
|
||||
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
||||
jq --null-input \
|
||||
--arg version "${VERSION}" \
|
||||
--arg pub_date "${PUB_DATE}" \
|
||||
--arg notes "${NOTES}" \
|
||||
--arg darwin_url "$RELEASE_DIR/dmg/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_universal.dmg" \
|
||||
--arg windows_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi" \
|
||||
--arg windows_aarch64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_arm64_en-US.msi" \
|
||||
'{
|
||||
"version": $version,
|
||||
"pub_date": $pub_date,
|
||||
"notes": $notes,
|
||||
"platforms": {
|
||||
"dmg-universal": {
|
||||
"url": $darwin_url
|
||||
},
|
||||
"msi-x86_64": {
|
||||
"url": $windows_x86_64_url
|
||||
},
|
||||
"msi-aarch64": {
|
||||
"url": $windows_aarch64_url
|
||||
}
|
||||
}
|
||||
}' > last_download.json
|
||||
cat last_download.json
|
||||
- name: List artifacts
|
||||
run: "ls -R out"
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
uses: 'google-github-actions/auth@v2.1.5'
|
||||
@ -352,21 +294,23 @@ jobs:
|
||||
- name: Set up Google Cloud SDK
|
||||
uses: google-github-actions/setup-gcloud@v2.1.0
|
||||
with:
|
||||
project_id: kittycadapi
|
||||
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
|
||||
|
||||
- name: Upload release files to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.3
|
||||
with:
|
||||
path: artifact
|
||||
glob: '*/Zoo*'
|
||||
path: out
|
||||
glob: 'Zoo*'
|
||||
parent: false
|
||||
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload update endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.3
|
||||
with:
|
||||
path: last_update.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
path: out
|
||||
glob: 'latest*'
|
||||
parent: false
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload download endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.3
|
||||
@ -374,11 +318,27 @@ jobs:
|
||||
path: last_download.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload release files to public bucket for tauri
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
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.1.1
|
||||
with:
|
||||
path: last_update.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload release files to Github
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: 'artifact/*/Zoo*'
|
||||
files: 'out/Zoo*'
|
||||
|
||||
# TODO: Add GitHub publisher
|
||||
|
||||
announce_release:
|
||||
needs: [publish-apps-release]
|
||||
|
12
.github/workflows/playwright.yml
vendored
@ -139,7 +139,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: playwright-report-ubuntu-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: playwright-report-${{ matrix.os }}-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
@ -174,14 +174,14 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
with:
|
||||
name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
- uses: actions/download-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run playwright/chrome flow (with retries)
|
||||
id: retry
|
||||
@ -244,14 +244,14 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
@ -351,7 +351,7 @@ jobs:
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-ubuntu-${{ github.sha }}
|
||||
name: test-results-${{ matrix.os }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run electron tests (with retries)
|
||||
id: retry
|
||||
|
1
.gitignore
vendored
@ -59,6 +59,7 @@ src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||
Mac_App_Distribution.provisionprofile
|
||||
|
||||
*.tsbuildinfo
|
||||
src/wasm-lib/pkg
|
||||
|
||||
venv
|
||||
.vite/
|
||||
|
@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
# From https://dev.to/rwwagner90/signing-electron-apps-with-github-actions-4cof
|
||||
|
||||
KEY_CHAIN=build.keychain
|
||||
CERTIFICATE_P12=certificate.p12
|
||||
|
||||
# Recreate the certificate from the secure environment variable
|
||||
echo $APPLE_CERTIFICATE | base64 --decode > $CERTIFICATE_P12
|
||||
|
||||
#create a keychain
|
||||
security create-keychain -p actions $KEY_CHAIN
|
||||
|
||||
# Make the keychain the default so identities are found
|
||||
security default-keychain -s $KEY_CHAIN
|
||||
|
||||
# Unlock the keychain
|
||||
security unlock-keychain -p actions $KEY_CHAIN
|
||||
|
||||
security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $APPLE_CERTIFICATE_PASSWORD -T /usr/bin/codesign;
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN
|
||||
|
||||
# remove certs
|
||||
rm -fr *.p12
|
@ -88,6 +88,7 @@ layout: manual
|
||||
* [`tan`](kcl/tan)
|
||||
* [`tangentialArc`](kcl/tangentialArc)
|
||||
* [`tangentialArcTo`](kcl/tangentialArcTo)
|
||||
* [`tangentialArcToRelative`](kcl/tangentialArcToRelative)
|
||||
* [`tau`](kcl/tau)
|
||||
* [`toDegrees`](kcl/toDegrees)
|
||||
* [`toRadians`](kcl/toRadians)
|
||||
|
6713
docs/kcl/std.json
@ -37,8 +37,7 @@ const example = extrude(10, exampleSketch)
|
||||
offset: number,
|
||||
// Radius of the arc. Not to be confused with Raiders of the Lost Ark.
|
||||
radius: number,
|
||||
} |
|
||||
[number, number]
|
||||
}
|
||||
```
|
||||
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
|
||||
```js
|
||||
|
863
docs/kcl/tangentialArcToRelative.md
Normal file
@ -96,33 +96,49 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
}
|
||||
|
||||
// deselect line tool
|
||||
await page.getByTestId('line').click()
|
||||
const btnLine = page.getByTestId('line')
|
||||
const btnLineAriaPressed = await btnLine.getAttribute('aria-pressed')
|
||||
if (btnLineAriaPressed === 'true') {
|
||||
await btnLine.click()
|
||||
}
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect
|
||||
.poll(async () => u.getGreatestPixDiff(line1, TEST_COLORS.WHITE))
|
||||
.toBeLessThan(3)
|
||||
await page.waitForTimeout(100)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(line1, [249, 249, 249]))
|
||||
.poll(async () => u.getGreatestPixDiff(line1, [249, 249, 249]))
|
||||
.toBeLessThan(3)
|
||||
await page.waitForTimeout(100)
|
||||
}
|
||||
|
||||
// click between first two clicks to get center of the line
|
||||
await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
|
||||
await expect(
|
||||
await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)
|
||||
).toBeLessThan(3)
|
||||
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
|
||||
}
|
||||
|
||||
// hold down shift
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click between the latest two clicks to get center of the line
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 20)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// selected two lines therefore there should be two cursors
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||
await page.waitForTimeout(100)
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Length: open menu' }).click()
|
||||
|
204
e2e/playwright/file-tree.spec.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('when using the file tree to', () => {
|
||||
const fromFile = 'main.kcl'
|
||||
const toFile = 'hello.kcl'
|
||||
|
||||
test(
|
||||
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
renameFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
|
||||
// File the main.kcl with contents
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
await renameFile(fromFile, toFile)
|
||||
await page.reload()
|
||||
|
||||
await test.step('Postcondition: editor has same content as before the rename', async () => {
|
||||
await editorTextMatches(kclCube)
|
||||
})
|
||||
|
||||
await test.step('Postcondition: opening and closing settings works', async () => {
|
||||
const settingsOpenButton = page.getByRole('link', {
|
||||
name: 'settings Settings',
|
||||
})
|
||||
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||
await settingsOpenButton.click()
|
||||
await settingsCloseButton.click()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`create many new untitled files they increment their names`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
const { panesOpen, createAndSelectProject, createNewFile } =
|
||||
await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
|
||||
await createNewFile('')
|
||||
await createNewFile('')
|
||||
await createNewFile('')
|
||||
await createNewFile('')
|
||||
await createNewFile('')
|
||||
|
||||
await test.step('Postcondition: there are 5 new Untitled-*.kcl files', async () => {
|
||||
await expect(
|
||||
page
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: /Untitled[-]?[0-5]?/ })
|
||||
).toHaveCount(5)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'create a new file with the same name as an existing file cancels the operation',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
createNewFileAndSelect,
|
||||
renameFile,
|
||||
selectFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
// File the main.kcl with contents
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
const kcl1 = 'main.kcl'
|
||||
const kcl2 = '2.kcl'
|
||||
|
||||
await createNewFileAndSelect(kcl2)
|
||||
const kclCylinder = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCylinder)
|
||||
|
||||
await renameFile(kcl2, kcl1)
|
||||
|
||||
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {
|
||||
await selectFile(kcl1)
|
||||
await editorTextMatches(kclCube)
|
||||
})
|
||||
|
||||
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
|
||||
await selectFile(kcl2)
|
||||
await editorTextMatches(kclCylinder)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'deleting all files recreates a default main.kcl with no code',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
deleteFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
// File the main.kcl with contents
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
const kcl1 = 'main.kcl'
|
||||
|
||||
await deleteFile(kcl1)
|
||||
|
||||
await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => {
|
||||
await editorTextMatches('')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
@ -511,10 +511,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
|
||||
editorTextMatches: async (code: string) => {
|
||||
const editor = page.locator(editorSelector)
|
||||
const editorText = await editor.textContent()
|
||||
return expect(util.toNormalizedCode(editorText || '')).toBe(
|
||||
util.toNormalizedCode(code)
|
||||
)
|
||||
return expect(editor).toHaveText(code, { useInnerText: true })
|
||||
},
|
||||
|
||||
pasteCodeInEditor: async (code: string) => {
|
||||
@ -532,18 +529,62 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
})
|
||||
},
|
||||
|
||||
createNewFile: async (name: string) => {
|
||||
return test?.step(`Create a file named ${name}`, async () => {
|
||||
await page.getByTestId('create-file-button').click()
|
||||
await page.getByTestId('file-rename-field').fill(name)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
},
|
||||
|
||||
selectFile: async (name: string) => {
|
||||
return test?.step(`Select ${name}`, async () => {
|
||||
await page
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: name })
|
||||
.click()
|
||||
})
|
||||
},
|
||||
|
||||
createNewFileAndSelect: async (name: string) => {
|
||||
return test?.step(`Create a file named ${name}, select it`, async () => {
|
||||
await page.getByTestId('create-file-button').click()
|
||||
await page.getByTestId('file-rename-field').fill(name)
|
||||
await page.keyboard.press('Enter')
|
||||
await page
|
||||
.getByTestId('file-pane-scroll-container')
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: name })
|
||||
.click()
|
||||
})
|
||||
},
|
||||
|
||||
renameFile: async (fromName: string, toName: string) => {
|
||||
return test?.step(`Rename ${fromName} to ${toName}`, async () => {
|
||||
await page
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: fromName })
|
||||
.click({ button: 'right' })
|
||||
await page.getByTestId('context-menu-rename').click()
|
||||
await page.getByTestId('file-rename-field').fill(toName)
|
||||
await page.keyboard.press('Enter')
|
||||
await page
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: toName })
|
||||
.click()
|
||||
})
|
||||
},
|
||||
|
||||
deleteFile: async (name: string) => {
|
||||
return test?.step(`Delete ${name}`, async () => {
|
||||
await page
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: name })
|
||||
.click({ button: 'right' })
|
||||
await page.getByTestId('context-menu-delete').click()
|
||||
await page.getByTestId('delete-confirmation').click()
|
||||
})
|
||||
},
|
||||
|
||||
panesOpen: async (paneIds: PaneId[]) => {
|
||||
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
||||
await page.addInitScript(
|
||||
@ -772,7 +813,6 @@ export async function setup(context: BrowserContext, page: Page) {
|
||||
localStorage.setItem('persistCode', ``)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
|
||||
console.log('TEST_SETTINGS.projects', settings)
|
||||
},
|
||||
{
|
||||
token: secrets.token,
|
||||
|
@ -773,9 +773,9 @@ const extrude001 = extrude(50, sketch001)
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
let noHoverColor: [number, number, number] = [82, 82, 82]
|
||||
let hoverColor: [number, number, number] = [116, 116, 116]
|
||||
let selectColor: [number, number, number] = [144, 148, 97]
|
||||
let noHoverColor: [number, number, number] = [92, 92, 92]
|
||||
let hoverColor: [number, number, number] = [127, 127, 127]
|
||||
let selectColor: [number, number, number] = [155, 155, 105]
|
||||
|
||||
const extrudeWall = { x: 670, y: 275 }
|
||||
const extrudeText = `line([170.36, -121.61], %, $seg01)`
|
||||
@ -816,9 +816,9 @@ const extrude001 = extrude(50, sketch001)
|
||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||
|
||||
// because of shading, color is not exact everywhere on the face
|
||||
noHoverColor = [104, 104, 104]
|
||||
hoverColor = [134, 134, 134]
|
||||
selectColor = [158, 162, 110]
|
||||
noHoverColor = [115, 115, 115]
|
||||
hoverColor = [145, 145, 145]
|
||||
selectColor = [168, 168, 120]
|
||||
|
||||
await expect(await u.getGreatestPixDiff(cap, noHoverColor)).toBeLessThan(6)
|
||||
await page.mouse.move(cap.x, cap.y)
|
||||
|
@ -1,11 +1,5 @@
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
tearDown,
|
||||
setupElectron,
|
||||
createProjectAndRenameIt,
|
||||
} from './test-utils'
|
||||
import { getUtils, setup, tearDown, setupElectron } from './test-utils'
|
||||
import { join } from 'path'
|
||||
import fs from 'fs'
|
||||
|
||||
@ -698,13 +692,16 @@ test(
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({ testInfo })
|
||||
const fileExists = () =>
|
||||
fs.existsSync(join(dir, 'test-000', 'lego-2x4.kcl'))
|
||||
fs.existsSync(join(dir, 'project-000', 'lego-2x4.kcl'))
|
||||
|
||||
const { createAndSelectProject, panesOpen } = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await panesOpen(['code', 'files'])
|
||||
|
||||
// Create and navigate to the project
|
||||
await createProjectAndRenameIt({ name: 'test-000', page })
|
||||
await page.getByTestId('project-link').click()
|
||||
await createAndSelectProject('project-000')
|
||||
|
||||
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
|
||||
await expect(
|
||||
@ -713,10 +710,6 @@ test(
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// Open the files pane
|
||||
const filesPaneButton = page.getByTestId('files-pane-button')
|
||||
await filesPaneButton.click()
|
||||
|
||||
await test.step(`Test file creation`, async () => {
|
||||
await sendPromptFromCommandBar(page, 'lego 2x4')
|
||||
// File is considered created if it shows up in the Project Files pane
|
||||
|
65
electron-builder.yml
Normal file
@ -0,0 +1,65 @@
|
||||
appId: dev.zoo.modeling-app
|
||||
|
||||
directories:
|
||||
output: out
|
||||
buildResources: assets
|
||||
|
||||
files:
|
||||
- .vite/**
|
||||
|
||||
mac:
|
||||
category: public.app-category.developer-tools
|
||||
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
|
||||
target:
|
||||
- target: dmg
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
- target: zip
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
notarize:
|
||||
teamId: 92H8YB3B95
|
||||
|
||||
win:
|
||||
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
|
||||
target:
|
||||
- target: nsis
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
- target: msi
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
signingHashAlgorithms:
|
||||
- sha256
|
||||
sign: "./sign-win.js"
|
||||
publisherName: "KittyCAD Inc" # needs to be exactly like on Digicert
|
||||
icon: "assets/icon.ico"
|
||||
|
||||
msi:
|
||||
oneClick: false
|
||||
perMachine: true
|
||||
|
||||
nsis:
|
||||
oneClick: false
|
||||
perMachine: true
|
||||
allowElevation: true
|
||||
license: "LICENSE"
|
||||
installerIcon: "assets/icon.ico"
|
||||
include: "./installer.nsh"
|
||||
|
||||
linux:
|
||||
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
|
||||
target:
|
||||
- target: appImage
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
|
||||
publish:
|
||||
- provider: generic
|
||||
url: https://dl.zoo.dev/releases/modeling-app/test/electron-builder
|
||||
channel: latest
|
8
installer.nsh
Normal file
@ -0,0 +1,8 @@
|
||||
!macro preInit
|
||||
SetRegView 64
|
||||
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
|
||||
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
|
||||
SetRegView 32
|
||||
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
|
||||
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
|
||||
!macroend
|
12
package.json
@ -39,6 +39,7 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"electron-updater": "^6.2.1",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
@ -97,7 +98,9 @@
|
||||
"tron:package": "electron-forge package",
|
||||
"tron:make": "electron-forge make",
|
||||
"tron:publish": "electron-forge publish",
|
||||
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron"
|
||||
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron",
|
||||
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
|
||||
"tronb:package": "electron-builder --config electron-builder.yml"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
@ -134,6 +137,7 @@
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.1",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@tauri-apps/cli": "^2.0.0-rc.9",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
"@types/d3-force": "^3.0.10",
|
||||
@ -150,7 +154,6 @@
|
||||
"@types/three": "^0.163.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/wait-on": "^5.3.4",
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
@ -161,6 +164,8 @@
|
||||
"autoprefixer": "^10.4.19",
|
||||
"d3-force": "^3.0.0",
|
||||
"electron": "^32.0.1",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-notarize": "^1.2.2",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
@ -172,7 +177,7 @@
|
||||
"node-fetch": "^3.3.2",
|
||||
"pixelmatch": "^5.3.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss": "^8.4.43",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.8.8",
|
||||
"setimmediate": "^1.0.5",
|
||||
@ -185,7 +190,6 @@
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.0",
|
||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||
"wait-on": "^7.2.0",
|
||||
"wasm-pack": "^0.13.0",
|
||||
"ws": "^8.17.0",
|
||||
"yarn": "^1.22.22"
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
timeout: 120_000, // override the default 30s timeout
|
||||
testDir: './e2e/playwright',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Do not retry */
|
||||
retries: process.env.CI ? 0 : 0,
|
||||
/* Different amount of parallelism on CI and local. */
|
||||
workers: process.env.CI ? 1 : 4,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
[process.env.CI ? 'dot' : 'list'],
|
||||
['json', { outputFile: './test-results/report.json' }],
|
||||
['html'],
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'retain-on-failure',
|
||||
actionTimeout: 15000,
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
})
|
BIN
public/wheel-loop-dark.mp4
Normal file
BIN
public/wheel-loop.mp4
Normal file
38
sign-win.js
Normal file
@ -0,0 +1,38 @@
|
||||
// From https://github.com/OpenBuilds/OpenBuilds-CONTROL/blob/4800540ffaa517925fc2cff26670809efa341ffe/signWin.js
|
||||
const { execSync } = require('node:child_process')
|
||||
|
||||
exports.default = async (configuration) => {
|
||||
if (!process.env.SM_API_KEY) {
|
||||
console.error(
|
||||
'Signing using signWin.js script: failed: SM_API_KEY ENV VAR NOT FOUND'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!process.env.WINDOWS_CERTIFICATE_THUMBPRINT) {
|
||||
console.error(
|
||||
'Signing using signWin.js script: failed: FINGERPRINT ENV VAR NOT FOUND'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!configuration.path) {
|
||||
throw new Error(
|
||||
`Signing using signWin.js script: failed: TARGET PATH NOT FOUND`
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(
|
||||
`smctl sign --fingerprint="${
|
||||
process.env.WINDOWS_CERTIFICATE_THUMBPRINT
|
||||
}" --input "${String(configuration.path)}"`,
|
||||
{
|
||||
stdio: 'inherit',
|
||||
}
|
||||
)
|
||||
console.log('Signing using signWin.js script: successful')
|
||||
} catch (error) {
|
||||
console.error('Signing using signWin.js script: failed:', error)
|
||||
}
|
||||
}
|
@ -119,6 +119,15 @@ export function App() {
|
||||
paneOpacity +
|
||||
(context.store?.buttonDownInStream ? ' pointer-events-none' : '')
|
||||
}
|
||||
// Override the electron window draggable region behavior as well
|
||||
// when the button is down in the stream
|
||||
style={
|
||||
{
|
||||
'-webkit-app-region': context.store?.buttonDownInStream
|
||||
? 'no-drag'
|
||||
: '',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
project={{ project, file }}
|
||||
enableMenu={true}
|
||||
/>
|
||||
|
@ -42,7 +42,13 @@ export type ActionButtonProps =
|
||||
|
||||
export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
|
||||
const classNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10 ${
|
||||
props.iconStart ? (props.iconEnd ? 'px-0' : 'pr-2') : 'px-2'
|
||||
props.iconStart
|
||||
? props.iconEnd
|
||||
? 'px-0'
|
||||
: 'pr-2'
|
||||
: props.iconEnd
|
||||
? 'px-2'
|
||||
: 'pl-2'
|
||||
} ${props.className ? props.className : ''}`
|
||||
|
||||
switch (props.Element) {
|
||||
|
@ -35,7 +35,7 @@ export const ActionIcon = ({
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
`w-fit inline-grid place-content-center ${className} ` +
|
||||
`w-fit self-stretch inline-grid place-content-center ${className} ` +
|
||||
computedBgClassName
|
||||
}
|
||||
>
|
||||
|
@ -4,21 +4,11 @@
|
||||
*/
|
||||
.header {
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
-webkit-app-region: drag; /* Make the header of the app draggable */
|
||||
}
|
||||
|
||||
.header button {
|
||||
-webkit-app-region: no-drag; /* Make the button not draggable */
|
||||
}
|
||||
|
||||
.header a {
|
||||
-webkit-app-region: no-drag; /* Make the link not draggable */
|
||||
}
|
||||
|
||||
.header textarea {
|
||||
-webkit-app-region: no-drag; /* Make the textarea not draggable */
|
||||
}
|
||||
|
||||
.header input {
|
||||
-webkit-app-region: no-drag; /* Make the input not draggable */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
/* 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
|
||||
* all interactive elements opt-out of this behavior by default in src/index.css
|
||||
*/
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ interface AppHeaderProps extends React.PropsWithChildren {
|
||||
project?: Omit<IndexLoaderData, 'code'>
|
||||
className?: string
|
||||
enableMenu?: boolean
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export const AppHeader = ({
|
||||
@ -19,6 +20,7 @@ export const AppHeader = ({
|
||||
project,
|
||||
children,
|
||||
className = '',
|
||||
style,
|
||||
enableMenu = false,
|
||||
}: AppHeaderProps) => {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
@ -33,6 +35,7 @@ export const AppHeader = ({
|
||||
' overlaid-panes sticky top-0 z-20 px-2 items-start ' +
|
||||
className
|
||||
}
|
||||
style={style}
|
||||
>
|
||||
<ProjectSidebarMenu
|
||||
enableMenu={enableMenu}
|
||||
|
@ -135,16 +135,15 @@ interface ContextMenuItemProps {
|
||||
icon?: ActionIconProps['icon']
|
||||
onClick?: () => void
|
||||
hotkey?: string
|
||||
'data-testid'?: string
|
||||
}
|
||||
|
||||
export function ContextMenuItem({
|
||||
children,
|
||||
icon,
|
||||
onClick,
|
||||
hotkey,
|
||||
}: ContextMenuItemProps) {
|
||||
export function ContextMenuItem(props: ContextMenuItemProps) {
|
||||
const { children, icon, onClick, hotkey } = props
|
||||
|
||||
return (
|
||||
<button
|
||||
data-testid={props['data-testid']}
|
||||
className="flex items-center gap-2 py-1 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
|
||||
onClick={onClick}
|
||||
>
|
||||
|
@ -332,7 +332,7 @@ const CustomIconMap = {
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.5 4C4.11929 4 3 5.11929 3 6.5V7C3 10.0376 5.46243 12.5 8.5 12.5H8.96482C9.46635 12.5 9.93469 12.2493 10.2129 11.8321L10.5173 11.3755C11.1396 12.0849 12.0423 12.5 13 12.5H13.75H15V14C15 14.2626 14.9483 14.5227 14.8478 14.7654C14.7472 15.008 14.5999 15.2285 14.4142 15.4142C14.2285 15.5999 14.008 15.7472 13.7654 15.8478C13.5227 15.9483 13.2626 16 13 16C12.7374 16 12.4773 15.9483 12.2346 15.8478C11.992 15.7472 11.7715 15.5999 11.5858 15.4142C11.4001 15.2285 11.2528 15.008 11.1522 14.7654C11.1164 14.6789 11.0868 14.5902 11.0635 14.5H11.8544C11.9168 14.6431 12.0056 14.7734 12.1161 14.8839C12.2322 15 12.37 15.092 12.5216 15.1548C12.6733 15.2177 12.8358 15.25 13 15.25C13.1642 15.25 13.3267 15.2177 13.4784 15.1548C13.63 15.092 13.7678 15 13.8839 14.8839C14 14.7678 14.092 14.63 14.1548 14.4784C14.2177 14.3267 14.25 14.1642 14.25 14V13H13.25V14C13.25 14.0328 13.2435 14.0653 13.231 14.0957C13.2184 14.126 13.2 14.1536 13.1768 14.1768C13.1536 14.2 13.126 14.2184 13.0957 14.231C13.0653 14.2435 13.0328 14.25 13 14.25C12.9672 14.25 12.9347 14.2435 12.9043 14.231C12.874 14.2184 12.8464 14.2 12.8232 14.1768C12.8 14.1536 12.7816 14.126 12.769 14.0957C12.7565 14.0653 12.75 14.0328 12.75 14V13.5H12.25H10.5H10V14C10 14.394 10.0776 14.7841 10.2284 15.1481C10.3791 15.512 10.6001 15.8427 10.8787 16.1213C11.1573 16.3999 11.488 16.6209 11.8519 16.7716C12.2159 16.9224 12.606 17 13 17C13.394 17 13.7841 16.9224 14.1481 16.7716C14.512 16.6209 14.8427 16.3999 15.1213 16.1213C15.3999 15.8427 15.6209 15.512 15.7716 15.1481C15.9224 14.7841 16 14.394 16 14V12.5H17V11.5H16V8.5C16 6.01472 13.9853 4 11.5 4H5.5ZM11.084 10.4746L10.9226 10.2326L9.42875 7.74275L8.57125 8.25725L9.90846 10.4859L9.38084 11.2773C9.28811 11.4164 9.13199 11.5 8.96482 11.5H8.5C6.01472 11.5 4 9.48528 4 7V6.5C4 5.67157 4.67157 5 5.5 5H11.5C13.433 5 15 6.567 15 8.5V11.5H13.75H13C12.2301 11.5 11.5111 11.1152 11.084 10.4746ZM13.5 8.5C13.5 9.05228 13.0523 9.5 12.5 9.5C11.9477 9.5 11.5 9.05228 11.5 8.5C11.5 7.94772 11.9477 7.5 12.5 7.5C13.0523 7.5 13.5 7.94772 13.5 8.5Z"
|
||||
fill="black"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
|
@ -16,7 +16,11 @@ import {
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { fileMachine } from 'machines/fileMachine'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { DEFAULT_FILE_NAME, FILE_EXT } from 'lib/constants'
|
||||
import {
|
||||
DEFAULT_FILE_NAME,
|
||||
DEFAULT_PROJECT_KCL_FILE,
|
||||
FILE_EXT,
|
||||
} from 'lib/constants'
|
||||
import { getProjectInfo } from 'lib/desktop'
|
||||
import { getNextDirName, getNextFileName } from 'lib/desktopFS'
|
||||
|
||||
@ -167,6 +171,25 @@ export const FileMachineProvider = ({
|
||||
name
|
||||
)
|
||||
|
||||
// no-op
|
||||
if (oldPath === newPath) {
|
||||
return {
|
||||
message: `Old is the same as new.`,
|
||||
newPath,
|
||||
oldPath,
|
||||
}
|
||||
}
|
||||
|
||||
// if there are any siblings with the same name, report error.
|
||||
const entries = await window.electron.readdir(
|
||||
window.electron.path.dirname(newPath)
|
||||
)
|
||||
for (let entry of entries) {
|
||||
if (entry === newName) {
|
||||
return Promise.reject(new Error('Filename already exists.'))
|
||||
}
|
||||
}
|
||||
|
||||
window.electron.rename(oldPath, newPath)
|
||||
|
||||
if (!file) {
|
||||
@ -209,6 +232,27 @@ export const FileMachineProvider = ({
|
||||
.catch((e) => console.error('Error deleting file', e))
|
||||
}
|
||||
|
||||
// If there are no more files at all in the project, create a main.kcl
|
||||
// for when we navigate to the root.
|
||||
if (!project?.path) {
|
||||
return Promise.reject(new Error('Project path not set.'))
|
||||
}
|
||||
|
||||
const entries = await window.electron.readdir(project.path)
|
||||
const hasKclEntries =
|
||||
entries.filter((e: string) => e.endsWith('.kcl')).length !== 0
|
||||
if (!hasKclEntries) {
|
||||
await window.electron.writeFile(
|
||||
window.electron.path.join(project.path, DEFAULT_PROJECT_KCL_FILE),
|
||||
''
|
||||
)
|
||||
// Refresh the route selected above because it's possible we're on
|
||||
// the same path on the navigate, which doesn't cause anything to
|
||||
// refresh, leaving a stale execution state.
|
||||
navigate(0)
|
||||
return
|
||||
}
|
||||
|
||||
// If we just deleted the current file or one of its parent directories,
|
||||
// navigate to the project root
|
||||
if (
|
||||
|
@ -358,10 +358,18 @@ function FileTreeContextMenu({
|
||||
<ContextMenu
|
||||
menuTargetElement={itemRef}
|
||||
items={[
|
||||
<ContextMenuItem onClick={onRename} hotkey="Enter">
|
||||
<ContextMenuItem
|
||||
data-testid="context-menu-rename"
|
||||
onClick={onRename}
|
||||
hotkey="Enter"
|
||||
>
|
||||
Rename
|
||||
</ContextMenuItem>,
|
||||
<ContextMenuItem onClick={onDelete} hotkey={metaKey + ' + Del'}>
|
||||
<ContextMenuItem
|
||||
data-testid="context-menu-delete"
|
||||
onClick={onDelete}
|
||||
hotkey={metaKey + ' + Del'}
|
||||
>
|
||||
Delete
|
||||
</ContextMenuItem>,
|
||||
]}
|
||||
|
@ -49,7 +49,11 @@ export const NetworkMachineIndicator = ({
|
||||
{Object.entries(machineManager.machines).map(
|
||||
([hostname, machine]) => (
|
||||
<li key={hostname} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
||||
<p className="">{machine.model || machine.manufacturer}</p>
|
||||
<p className="">
|
||||
{machine.make_model.model ||
|
||||
machine.make_model.manufacturer ||
|
||||
'Unknown Machine'}
|
||||
</p>
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
Hostname {hostname}
|
||||
</p>
|
||||
|
@ -217,7 +217,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
</p>
|
||||
{displayedName !== user.email && (
|
||||
<p
|
||||
className="m-0 text-chalkboard-70 dark:text-chalkboard-40 text-xs"
|
||||
className="m-0 overflow-ellipsis overflow-hidden text-chalkboard-70 dark:text-chalkboard-40 text-xs"
|
||||
data-testid="email"
|
||||
>
|
||||
{user.email}
|
||||
|
@ -9,6 +9,7 @@ import { useModelingContext } from './useModelingContext'
|
||||
import { getEventForSelectWithPoint } from 'lib/selections'
|
||||
import {
|
||||
getCapCodeRef,
|
||||
getExtrudeEdgeCodeRef,
|
||||
getExtrusionFromSuspectedExtrudeSurface,
|
||||
getSolid2dCodeRef,
|
||||
getWallCodeRef,
|
||||
@ -60,6 +61,13 @@ export function useEngineConnectionSubscriptions() {
|
||||
? [codeRef.range]
|
||||
: [codeRef.range, extrusion.codeRef.range]
|
||||
)
|
||||
} else if (artifact?.type === 'extrudeEdge') {
|
||||
const codeRef = getExtrudeEdgeCodeRef(
|
||||
artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return
|
||||
editorManager.setHighlightRange([codeRef.range])
|
||||
} else if (artifact?.type === 'segment') {
|
||||
editorManager.setHighlightRange([
|
||||
artifact?.codeRef?.range || [0, 0],
|
||||
|
@ -4,6 +4,18 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
a {
|
||||
/* Make all interactive elements not act as handles
|
||||
* to drag the electron app window by default,
|
||||
* per the electron docs: https://www.electronjs.org/docs/latest/tutorial/window-customization#set-custom-draggable-region
|
||||
*/
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
@apply font-sans;
|
||||
@ -97,7 +109,7 @@ button:disabled {
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-primary underline hover:hue-rotate-15;
|
||||
@apply text-primary hover:hue-rotate-15;
|
||||
}
|
||||
|
||||
.dark a {
|
||||
@ -274,6 +286,35 @@ code {
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Modified from the very helpful https://www.transition.style/#in:circle:hesitate */
|
||||
@keyframes circle-in-hesitate {
|
||||
0% {
|
||||
clip-path: circle(
|
||||
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-editor {
|
||||
height: 100% !important;
|
||||
|
@ -4,6 +4,7 @@ import { KCLError, kclErrorsToDiagnostics } from './errors'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { EngineCommandManager } from './std/engineConnection'
|
||||
import { err } from 'lib/trap'
|
||||
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
|
||||
|
||||
import {
|
||||
CallExpression,
|
||||
@ -122,6 +123,7 @@ export class KclManager {
|
||||
get isExecuting() {
|
||||
return this._isExecuting
|
||||
}
|
||||
|
||||
set isExecuting(isExecuting) {
|
||||
this._isExecuting = isExecuting
|
||||
// If we have finished executing, but the execute is stale, we should
|
||||
@ -232,6 +234,12 @@ export class KclManager {
|
||||
async executeAst(args: ExecuteArgs = {}): Promise<void> {
|
||||
if (this.isExecuting) {
|
||||
this.executeIsStale = args
|
||||
|
||||
// The previous execteAst will be rejected and cleaned up. The execution will be marked as stale.
|
||||
// A new executeAst will start.
|
||||
this.engineCommandManager.rejectAllModelingCommands(
|
||||
EXECUTE_AST_INTERRUPT_ERROR_MESSAGE
|
||||
)
|
||||
// Exit early if we are already executing.
|
||||
return
|
||||
}
|
||||
@ -245,44 +253,38 @@ export class KclManager {
|
||||
// Make sure we clear before starting again. End session will do this.
|
||||
this.engineCommandManager?.endSession()
|
||||
await this.ensureWasmInit()
|
||||
const { logs, errors, programMemory } = await executeAst({
|
||||
const { logs, errors, programMemory, isInterrupted } = await executeAst({
|
||||
ast,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
})
|
||||
|
||||
this.lints = await lintAst({ ast: ast })
|
||||
// Program was not interrupted, setup the scene
|
||||
// Do not send send scene commands if the program was interrupted, go to clean up
|
||||
if (!isInterrupted) {
|
||||
this.lints = await lintAst({ ast: ast })
|
||||
|
||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||
defaultSelectionFilter(programMemory, this.engineCommandManager)
|
||||
await this.engineCommandManager.waitForAllCommands()
|
||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||
defaultSelectionFilter(programMemory, this.engineCommandManager)
|
||||
|
||||
if (args.zoomToFit) {
|
||||
let zoomObjectId: string | undefined = ''
|
||||
if (args.zoomOnRangeAndType) {
|
||||
zoomObjectId = this.engineCommandManager?.mapRangeToObjectId(
|
||||
args.zoomOnRangeAndType.range,
|
||||
args.zoomOnRangeAndType.type
|
||||
)
|
||||
if (args.zoomToFit) {
|
||||
let zoomObjectId: string | undefined = ''
|
||||
if (args.zoomOnRangeAndType) {
|
||||
zoomObjectId = this.engineCommandManager?.mapRangeToObjectId(
|
||||
args.zoomOnRangeAndType.range,
|
||||
args.zoomOnRangeAndType.type
|
||||
)
|
||||
}
|
||||
|
||||
await this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'zoom_to_fit',
|
||||
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
|
||||
padding: 0.1, // padding around the objects
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'zoom_to_fit',
|
||||
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
|
||||
padding: 0.1, // padding around the objects
|
||||
},
|
||||
})
|
||||
await this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'zoom_to_fit',
|
||||
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
|
||||
padding: 0.1, // padding around the objects
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
this.isExecuting = false
|
||||
@ -293,7 +295,8 @@ export class KclManager {
|
||||
return
|
||||
}
|
||||
this.logs = logs
|
||||
this.addKclErrors(errors)
|
||||
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
||||
this.addKclErrors(isInterrupted ? [] : errors)
|
||||
this.programMemory = programMemory
|
||||
this.ast = { ...ast }
|
||||
this._executeCallback()
|
||||
@ -301,6 +304,7 @@ export class KclManager {
|
||||
type: 'execution-done',
|
||||
data: null,
|
||||
})
|
||||
|
||||
this._cancelTokens.delete(currentExecutionId)
|
||||
}
|
||||
// NOTE: this always updates the code state and editor.
|
||||
|
@ -54,10 +54,12 @@ export async function executeAst({
|
||||
engineCommandManager: EngineCommandManager
|
||||
useFakeExecutor?: boolean
|
||||
programMemoryOverride?: ProgramMemory
|
||||
isInterrupted?: boolean
|
||||
}): Promise<{
|
||||
logs: string[]
|
||||
errors: KCLError[]
|
||||
programMemory: ProgramMemory
|
||||
isInterrupted: boolean
|
||||
}> {
|
||||
try {
|
||||
if (!useFakeExecutor) {
|
||||
@ -73,13 +75,23 @@ export async function executeAst({
|
||||
logs: [],
|
||||
errors: [],
|
||||
programMemory,
|
||||
isInterrupted: false,
|
||||
}
|
||||
} catch (e: any) {
|
||||
let isInterrupted = false
|
||||
if (e instanceof KCLError) {
|
||||
// Detect if it is a force interrupt error which is not a KCL processing error.
|
||||
if (
|
||||
e.msg ===
|
||||
'Failed to wait for promise from engine: JsValue("Force interrupt, executionIsStale, new AST requested")'
|
||||
) {
|
||||
isInterrupted = true
|
||||
}
|
||||
return {
|
||||
errors: [e],
|
||||
logs: [],
|
||||
programMemory: ProgramMemory.empty(),
|
||||
isInterrupted,
|
||||
}
|
||||
} else {
|
||||
console.log(e)
|
||||
@ -87,6 +99,7 @@ export async function executeAst({
|
||||
logs: [e],
|
||||
errors: [],
|
||||
programMemory: ProgramMemory.empty(),
|
||||
isInterrupted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ const extrude001 = extrude(-15, sketch001)`
|
||||
selectedSegmentSnippet,
|
||||
expectedExtrudeSnippet
|
||||
)
|
||||
})
|
||||
}, 5_000)
|
||||
it('should return the correct paths for a valid selection and extrusion in case of several extrusions and sketches', async () => {
|
||||
const code = `const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-30, 30], %)
|
||||
|
@ -58,7 +58,10 @@ Map {
|
||||
92,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
@ -77,7 +80,10 @@ Map {
|
||||
],
|
||||
},
|
||||
"edgeCutId": "UUID",
|
||||
"edgeIds": [],
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
@ -95,7 +101,10 @@ Map {
|
||||
156,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
@ -113,7 +122,10 @@ Map {
|
||||
209,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
@ -152,7 +164,16 @@ Map {
|
||||
266,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"surfaceIds": [
|
||||
"UUID",
|
||||
@ -209,6 +230,54 @@ Map {
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-15" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-16" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-17" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-18" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-19" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-20" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-21" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-22" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-23" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
@ -226,7 +295,7 @@ Map {
|
||||
"subType": "fillet",
|
||||
"type": "edgeCut",
|
||||
},
|
||||
"UUID-16" => {
|
||||
"UUID-24" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
@ -250,7 +319,7 @@ Map {
|
||||
"solid2dId": "UUID",
|
||||
"type": "path",
|
||||
},
|
||||
"UUID-17" => {
|
||||
"UUID-25" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
@ -263,12 +332,15 @@ Map {
|
||||
416,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-18" => {
|
||||
"UUID-26" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
@ -281,12 +353,15 @@ Map {
|
||||
438,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-19" => {
|
||||
"UUID-27" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
@ -299,12 +374,15 @@ Map {
|
||||
491,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-20" => {
|
||||
"UUID-28" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
@ -321,11 +399,11 @@ Map {
|
||||
"pathId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-21" => {
|
||||
"UUID-29" => {
|
||||
"pathId": "UUID",
|
||||
"type": "solid2D",
|
||||
},
|
||||
"UUID-22" => {
|
||||
"UUID-30" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
@ -338,7 +416,14 @@ Map {
|
||||
546,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"surfaceIds": [
|
||||
"UUID",
|
||||
@ -349,40 +434,76 @@ Map {
|
||||
],
|
||||
"type": "extrusion",
|
||||
},
|
||||
"UUID-23" => {
|
||||
"UUID-31" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-24" => {
|
||||
"UUID-32" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-25" => {
|
||||
"UUID-33" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-26" => {
|
||||
"UUID-34" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"subType": "start",
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-27" => {
|
||||
"UUID-35" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"subType": "end",
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-36" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-37" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-38" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-39" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-40" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
"UUID-41" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -247,7 +247,7 @@ describe('testing createArtifactGraph', () => {
|
||||
// of the edges refers to a non-existent node, the graph will throw.
|
||||
// further more we can check that each edge is bi-directional, if it's not
|
||||
// by checking the arrow heads going both ways, on the graph.
|
||||
await GraphTheGraph(theMap, 1400, 1400, 'exampleCode1.png')
|
||||
await GraphTheGraph(theMap, 2000, 2000, 'exampleCode1.png')
|
||||
}, 20000)
|
||||
})
|
||||
})
|
||||
@ -271,7 +271,7 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
|
||||
// of the edges refers to a non-existent node, the graph will throw.
|
||||
// further more we can check that each edge is bi-directional, if it's not
|
||||
// by checking the arrow heads going both ways, on the graph.
|
||||
await GraphTheGraph(theMap, 2500, 2500, 'sketchOnFaceOnFaceEtc.png')
|
||||
await GraphTheGraph(theMap, 3000, 3000, 'sketchOnFaceOnFaceEtc.png')
|
||||
}, 20000)
|
||||
})
|
||||
})
|
||||
@ -603,7 +603,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'segment',
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [98, 125],
|
||||
pathToNode: [['body', '']],
|
||||
@ -623,7 +623,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'segment',
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [162, 209],
|
||||
pathToNode: [['body', '']],
|
||||
@ -633,7 +633,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [243, 266],
|
||||
pathToNode: [['body', '']],
|
||||
@ -650,7 +650,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'segment',
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [131, 156],
|
||||
pathToNode: [['body', '']],
|
||||
@ -660,7 +660,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [243, 266],
|
||||
pathToNode: [['body', '']],
|
||||
@ -677,7 +677,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'segment',
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [98, 125],
|
||||
pathToNode: [['body', '']],
|
||||
@ -688,7 +688,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [243, 266],
|
||||
pathToNode: [['body', '']],
|
||||
@ -705,7 +705,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'segment',
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [76, 92],
|
||||
pathToNode: [['body', '']],
|
||||
@ -715,7 +715,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [243, 266],
|
||||
pathToNode: [['body', '']],
|
||||
@ -732,7 +732,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [243, 266],
|
||||
pathToNode: [['body', '']],
|
||||
@ -749,7 +749,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: [],
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [243, 266],
|
||||
pathToNode: [['body', '']],
|
||||
|
@ -91,7 +91,7 @@ interface ExtrudeEdge {
|
||||
type: 'extrudeEdge'
|
||||
segId: string
|
||||
extrusionId: string
|
||||
edgeId: string
|
||||
subType: 'opposite' | 'adjacent'
|
||||
}
|
||||
|
||||
/** A edgeCut is a more generic term for both fillet or chamfer */
|
||||
@ -422,6 +422,56 @@ export function getArtifactsToUpdate({
|
||||
}
|
||||
})
|
||||
return returnArr
|
||||
} else if (
|
||||
// is opposite edge
|
||||
(cmd.type === 'solid3d_get_opposite_edge' &&
|
||||
response.type === 'modeling' &&
|
||||
response.data.modeling_response.type === 'solid3d_get_opposite_edge' &&
|
||||
response.data.modeling_response.data.edge) ||
|
||||
// or is adjacent edge
|
||||
(cmd.type === 'solid3d_get_prev_adjacent_edge' &&
|
||||
response.type === 'modeling' &&
|
||||
response.data.modeling_response.type ===
|
||||
'solid3d_get_prev_adjacent_edge' &&
|
||||
response.data.modeling_response.data.edge)
|
||||
) {
|
||||
const wall = getArtifact(cmd.face_id)
|
||||
if (wall?.type !== 'wall') return returnArr
|
||||
const extrusion = getArtifact(wall.extrusionId)
|
||||
if (extrusion?.type !== 'extrusion') return returnArr
|
||||
const path = getArtifact(extrusion.pathId)
|
||||
if (path?.type !== 'path') return returnArr
|
||||
const segment = getArtifact(cmd.edge_id)
|
||||
if (segment?.type !== 'segment') return returnArr
|
||||
|
||||
return [
|
||||
{
|
||||
id: response.data.modeling_response.data.edge,
|
||||
artifact: {
|
||||
type: 'extrudeEdge',
|
||||
subType:
|
||||
cmd.type === 'solid3d_get_prev_adjacent_edge'
|
||||
? 'adjacent'
|
||||
: 'opposite',
|
||||
segId: cmd.edge_id,
|
||||
extrusionId: path.extrusionId,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: cmd.edge_id,
|
||||
artifact: {
|
||||
...segment,
|
||||
edgeIds: [response.data.modeling_response.data.edge],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: path.extrusionId,
|
||||
artifact: {
|
||||
...extrusion,
|
||||
edgeIds: [response.data.modeling_response.data.edge],
|
||||
},
|
||||
},
|
||||
]
|
||||
} else if (cmd.type === 'solid3d_fillet_edge') {
|
||||
returnArr.push({
|
||||
id,
|
||||
@ -655,6 +705,18 @@ export function getWallCodeRef(
|
||||
return seg.codeRef
|
||||
}
|
||||
|
||||
export function getExtrudeEdgeCodeRef(
|
||||
edge: ExtrudeEdge,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CommonCommandProperties | Error {
|
||||
const seg = getArtifactOfTypes(
|
||||
{ key: edge.segId, types: ['segment'] },
|
||||
artifactGraph
|
||||
)
|
||||
if (err(seg)) return seg
|
||||
return seg.codeRef
|
||||
}
|
||||
|
||||
export function getExtrusionFromSuspectedExtrudeSurface(
|
||||
id: string,
|
||||
artifactGraph: ArtifactGraph
|
||||
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 380 KiB |
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 617 KiB |
@ -16,6 +16,8 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { exportMake } from 'lib/exportMake'
|
||||
import toast from 'react-hot-toast'
|
||||
import { SettingsViaQueryString } from 'lib/settings/settingsTypes'
|
||||
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
|
||||
import { KclManager } from 'lang/KclSingleton'
|
||||
|
||||
// TODO(paultag): This ought to be tweakable.
|
||||
const pingIntervalMs = 5_000
|
||||
@ -1279,6 +1281,7 @@ interface PendingMessage {
|
||||
resolve: (data: [Models['WebSocketResponse_type']]) => void
|
||||
reject: (reason: string) => void
|
||||
promise: Promise<[Models['WebSocketResponse_type']]>
|
||||
isSceneCommand: boolean
|
||||
}
|
||||
export class EngineCommandManager extends EventTarget {
|
||||
/**
|
||||
@ -1379,6 +1382,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
}: CustomEvent<NewTrackArgs>) => {}
|
||||
modelingSend: ReturnType<typeof useModelingContext>['send'] =
|
||||
(() => {}) as any
|
||||
kclManager: null | KclManager = null
|
||||
|
||||
set exportIntent(intent: ExportIntent | null) {
|
||||
this._exportIntent = intent
|
||||
@ -1932,11 +1936,21 @@ export class EngineCommandManager extends EventTarget {
|
||||
;(cmd as any).sequence = this.outSequence++
|
||||
}
|
||||
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
||||
return this.sendCommand(command.cmd_id, {
|
||||
command,
|
||||
idToRangeMap: {},
|
||||
range: [0, 0],
|
||||
}).then(([a]) => a)
|
||||
return this.sendCommand(
|
||||
command.cmd_id,
|
||||
{
|
||||
command,
|
||||
idToRangeMap: {},
|
||||
range: [0, 0],
|
||||
},
|
||||
true // isSceneCommand
|
||||
)
|
||||
.then(([a]) => a)
|
||||
.catch((e) => {
|
||||
// TODO: Previously was never caught, we are not rejecting these pendingCommands but this needs to be handled at some point.
|
||||
/*noop*/
|
||||
return null
|
||||
})
|
||||
}
|
||||
/**
|
||||
* A wrapper around the sendCommand where all inputs are JSON strings
|
||||
@ -1963,6 +1977,12 @@ export class EngineCommandManager extends EventTarget {
|
||||
const idToRangeMap: { [key: string]: SourceRange } =
|
||||
JSON.parse(idToRangeStr)
|
||||
|
||||
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
|
||||
// Used in conjunction with rejectAllModelingCommands
|
||||
if (this?.kclManager?.executeIsStale) {
|
||||
return Promise.reject(EXECUTE_AST_INTERRUPT_ERROR_MESSAGE)
|
||||
}
|
||||
|
||||
const resp = await this.sendCommand(id, {
|
||||
command,
|
||||
range,
|
||||
@ -1980,7 +2000,8 @@ export class EngineCommandManager extends EventTarget {
|
||||
command: PendingMessage['command']
|
||||
range: PendingMessage['range']
|
||||
idToRangeMap: PendingMessage['idToRangeMap']
|
||||
}
|
||||
},
|
||||
isSceneCommand = false
|
||||
): Promise<[Models['WebSocketResponse_type']]> {
|
||||
const { promise, resolve, reject } = promiseFactory<any>()
|
||||
this.pendingCommands[id] = {
|
||||
@ -1990,7 +2011,9 @@ export class EngineCommandManager extends EventTarget {
|
||||
command: message.command,
|
||||
range: message.range,
|
||||
idToRangeMap: message.idToRangeMap,
|
||||
isSceneCommand,
|
||||
}
|
||||
|
||||
if (message.command.type === 'modeling_cmd_req') {
|
||||
this.orderedCommands.push({
|
||||
command: message.command,
|
||||
@ -2037,6 +2060,19 @@ export class EngineCommandManager extends EventTarget {
|
||||
this.deferredArtifactPopulated(null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject all of the modeling pendingCommands created from sendModelingCommandFromWasm
|
||||
* This interrupts the runtime of executeAst. Stops the AST processing and stops sending commands
|
||||
* to the engine
|
||||
*/
|
||||
rejectAllModelingCommands(rejectionMessage: string) {
|
||||
Object.values(this.pendingCommands).forEach(
|
||||
({ reject, isSceneCommand }) =>
|
||||
!isSceneCommand && reject(rejectionMessage)
|
||||
)
|
||||
}
|
||||
|
||||
async initPlanes() {
|
||||
if (this.planesInitialized()) return
|
||||
const planes = await this.makeDefaultPlanes()
|
||||
|
@ -25,7 +25,7 @@ export type ModelingCommandSchema = {
|
||||
storage?: StorageUnion
|
||||
}
|
||||
Make: {
|
||||
machine: components['schemas']['Machine']
|
||||
machine: components['schemas']['MachineInfoResponse']
|
||||
}
|
||||
Extrude: {
|
||||
selection: Selections // & { type: 'face' } would be cool to lock that down
|
||||
@ -179,21 +179,25 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
machine: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
valueSummary: (machine: components['schemas']['Machine']) =>
|
||||
machine.model || machine.manufacturer,
|
||||
valueSummary: (machine: components['schemas']['MachineInfoResponse']) =>
|
||||
machine.make_model.model ||
|
||||
machine.make_model.manufacturer ||
|
||||
'Unknown Machine',
|
||||
options: () => {
|
||||
return Object.entries(machineManager.machines).map(
|
||||
([hostname, machine]) => ({
|
||||
name: `${machine.model || machine.manufacturer}, ${hostname}`,
|
||||
([_, machine]) => ({
|
||||
name: `${machine.id} (${
|
||||
machine.make_model.model || machine.make_model.manufacturer
|
||||
}) via ${machineManager.machineApiIp || 'the local network'}`,
|
||||
isCurrent: false,
|
||||
value: machine as components['schemas']['Machine'],
|
||||
value: machine as components['schemas']['MachineInfoResponse'],
|
||||
})
|
||||
)
|
||||
},
|
||||
defaultValue: () => {
|
||||
return Object.values(
|
||||
machineManager.machines
|
||||
)[0] as components['schemas']['Machine']
|
||||
)[0] as components['schemas']['MachineInfoResponse']
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -8,6 +8,7 @@ export const MAX_PADDING = 7
|
||||
* This is available for users to edit as a setting.
|
||||
*/
|
||||
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
|
||||
export const DEFAULT_PROJECT_KCL_FILE = 'main.kcl'
|
||||
/** Name given the temporary "project" in the browser version of the app */
|
||||
export const BROWSER_PROJECT_NAME = 'browser'
|
||||
/** Name given the temporary file in the browser version of the app */
|
||||
@ -66,3 +67,8 @@ export const COOKIE_NAME = '__Secure-next-auth.session-token'
|
||||
|
||||
/** localStorage key to determine if we're in Playwright tests */
|
||||
export const PLAYWRIGHT_KEY = 'playwright'
|
||||
|
||||
/** Custom error message to match when rejectAllModelCommands is called
|
||||
* allows us to match if the execution of executeAst was interrupted */
|
||||
export const EXECUTE_AST_INTERRUPT_ERROR_MESSAGE =
|
||||
'Force interrupt, executionIsStale, new AST requested'
|
||||
|
@ -31,11 +31,11 @@ const bracket = startSketchOn('XY')
|
||||
|> extrude(width, %)
|
||||
|> fillet({
|
||||
radius: filletR,
|
||||
tags: [getPreviousAdjacentEdge(innerEdge)]
|
||||
tags: [getNextAdjacentEdge(innerEdge)]
|
||||
}, %)
|
||||
|> fillet({
|
||||
radius: filletR + thickness,
|
||||
tags: [getPreviousAdjacentEdge(outerEdge)]
|
||||
tags: [getNextAdjacentEdge(outerEdge)]
|
||||
}, %)`
|
||||
|
||||
/**
|
||||
|
@ -26,15 +26,7 @@ export async function exportMake(data: ArrayBuffer): Promise<Response | null> {
|
||||
return null
|
||||
}
|
||||
|
||||
let machineId = null
|
||||
if ('id' in currentMachine) {
|
||||
machineId = currentMachine.id
|
||||
} else if ('hostname' in currentMachine && currentMachine.hostname) {
|
||||
machineId = currentMachine.hostname
|
||||
} else if ('ip' in currentMachine && currentMachine.ip) {
|
||||
machineId = currentMachine.ip
|
||||
}
|
||||
|
||||
let machineId = currentMachine?.id
|
||||
if (!machineId) {
|
||||
console.error('No machine id available', currentMachine)
|
||||
toast.error('No machine id available')
|
||||
|
851
src/lib/machine-api.d.ts
vendored
@ -93,587 +93,56 @@ export interface paths {
|
||||
export type webhooks = Record<string, never>
|
||||
export interface components {
|
||||
schemas: {
|
||||
/** @description The type of accessory. */
|
||||
AccessoryType: 'none'
|
||||
/** @description Error information from a response. */
|
||||
Error: {
|
||||
error_code?: string
|
||||
message: string
|
||||
request_id: string
|
||||
}
|
||||
/** @description An info command. */
|
||||
Info: {
|
||||
/** @enum {string} */
|
||||
command: 'get_version'
|
||||
/** @description The info module. */
|
||||
module: components['schemas']['InfoModule'][]
|
||||
/** @description The reason of the info command. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the info command. */
|
||||
result?: components['schemas']['Result'] | null
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
/** @description Extra machine-specific information regarding a connected machine. */
|
||||
ExtraMachineInfoResponse:
|
||||
| {
|
||||
Moonraker: Record<string, never>
|
||||
}
|
||||
| {
|
||||
Usb: Record<string, never>
|
||||
}
|
||||
| {
|
||||
Bambu: Record<string, never>
|
||||
}
|
||||
/** @description Information regarding a connected machine. */
|
||||
MachineInfoResponse: {
|
||||
/** @description Additional, per-machine information which is specific to the underlying machine type. */
|
||||
extra?: components['schemas']['ExtraMachineInfoResponse'] | null
|
||||
/** @description Machine Identifier (ID) for the specific Machine. */
|
||||
id: string
|
||||
/** @description Information regarding the method of manufacture. */
|
||||
machine_type: components['schemas']['MachineType']
|
||||
/** @description Information regarding the make and model of the attached Machine. */
|
||||
make_model: components['schemas']['MachineMakeModel']
|
||||
/** @description Maximum part size that can be manufactured by this device. This may be some sort of theoretical upper bound, getting close to this limit seems like maybe a bad idea.
|
||||
*
|
||||
* This may be `None` if the maximum size is not knowable by the Machine API.
|
||||
*
|
||||
* What "close" means is up to you! */
|
||||
max_part_volume?: components['schemas']['Volume'] | null
|
||||
}
|
||||
/** @description An info module. */
|
||||
InfoModule: {
|
||||
/** @description The hardware version. */
|
||||
hw_ver: string
|
||||
/** @description The loader version. */
|
||||
loader_ver?: string | null
|
||||
/** @description The module name. */
|
||||
name: string
|
||||
/** @description The ota version. */
|
||||
ota_ver?: string | null
|
||||
/** @description The project name. */
|
||||
project_name?: string | null
|
||||
/** @description The serial number. */
|
||||
sn: string
|
||||
/** @description The software version. */
|
||||
sw_ver: string
|
||||
}
|
||||
/** @description The mode for the led. */
|
||||
LedMode: 'on' | 'off' | 'flashing'
|
||||
/** @description The node for the led. */
|
||||
LedNode: 'chamber_light' | 'work_light'
|
||||
/** @description A liveview message. */
|
||||
LiveView: {
|
||||
/** @enum {string} */
|
||||
command: 'init'
|
||||
/** @description The op protocols. */
|
||||
op_protocols: components['schemas']['OperationProtocol'][]
|
||||
/** @description The peer host. */
|
||||
peer_host: string
|
||||
/** @description The reason for the message. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description Details for a 3d printer connected over USB. */
|
||||
Machine:
|
||||
| {
|
||||
id: string
|
||||
manufacturer: string
|
||||
model: string
|
||||
port: string
|
||||
/** @enum {string} */
|
||||
type: 'UsbPrinter'
|
||||
}
|
||||
| {
|
||||
/** @description The hostname of the printer. */
|
||||
hostname?: string | null
|
||||
/**
|
||||
* Format: ip
|
||||
* @description The IP address of the printer.
|
||||
*/
|
||||
ip: string
|
||||
/** @description The manufacturer of the printer. */
|
||||
manufacturer: components['schemas']['NetworkPrinterManufacturer']
|
||||
/** @description The model of the printer. */
|
||||
model?: string | null
|
||||
/**
|
||||
* Format: uint16
|
||||
* @description The port of the printer.
|
||||
*/
|
||||
port?: number | null
|
||||
/** @description The serial number of the printer. */
|
||||
serial?: string | null
|
||||
/** @enum {string} */
|
||||
type: 'NetworkPrinter'
|
||||
}
|
||||
/** @description A message from a machine. */
|
||||
Message:
|
||||
| {
|
||||
UsbPrinter: components['schemas']['Message2']
|
||||
}
|
||||
| {
|
||||
NetworkPrinter: components['schemas']['Message3']
|
||||
}
|
||||
/**
|
||||
* @description A message from the printer.
|
||||
* @enum {string}
|
||||
*/
|
||||
Message2: 'ok'
|
||||
/** @description A message from the printer. */
|
||||
Message3:
|
||||
| {
|
||||
Bambu: components['schemas']['Message4']
|
||||
}
|
||||
| {
|
||||
Formlabs: Record<string, never>
|
||||
}
|
||||
/** @description A message from/to the printer. */
|
||||
Message4:
|
||||
| {
|
||||
print: components['schemas']['Print']
|
||||
}
|
||||
| {
|
||||
info: components['schemas']['Info']
|
||||
}
|
||||
| {
|
||||
system: components['schemas']['System']
|
||||
}
|
||||
| {
|
||||
security: components['schemas']['Security']
|
||||
}
|
||||
| {
|
||||
live_view: components['schemas']['LiveView']
|
||||
}
|
||||
| {
|
||||
json: unknown
|
||||
}
|
||||
| {
|
||||
unknown: string | null
|
||||
}
|
||||
/** @description Network printer manufacturer. */
|
||||
NetworkPrinterManufacturer: 'Bambu' | 'Formlabs'
|
||||
/** @description A nozzle type. */
|
||||
NozzleType: 'hardened_steel' | 'stainless_steel'
|
||||
/** @description An operation protocol. */
|
||||
OperationProtocol: {
|
||||
/** @description The protocol. */
|
||||
protocol: string
|
||||
/** @description The version. */
|
||||
version: string
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
/** @description Information regarding the make/model of a discovered endpoint. */
|
||||
MachineMakeModel: {
|
||||
/** @description The manufacturer that built the connected Machine. */
|
||||
manufacturer?: string | null
|
||||
/** @description The model of the connected Machine. */
|
||||
model?: string | null
|
||||
/** @description The unique serial number of the connected Machine. */
|
||||
serial?: string | null
|
||||
}
|
||||
/** @description Specific technique by which this Machine takes a design, and produces a real-world 3D object. */
|
||||
MachineType: 'Stereolithography' | 'FusedDeposition' | 'Cnc'
|
||||
/** @description The response from the `/ping` endpoint. */
|
||||
Pong: {
|
||||
/** @description The pong response. */
|
||||
message: string
|
||||
}
|
||||
/** @description A print command. */
|
||||
Print:
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'ams_control'
|
||||
/** @description The param. */
|
||||
param?: string | null
|
||||
/** @description The reason for the message. */
|
||||
reason: components['schemas']['Reason']
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'ams_change_filament'
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The error number.
|
||||
*/
|
||||
errorno?: number | null
|
||||
/** @description The reason for the message. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The target temperature.
|
||||
*/
|
||||
tar_temp?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The target.
|
||||
*/
|
||||
target: number
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'calibration'
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The option.
|
||||
*/
|
||||
option: number
|
||||
/** @description The reason for the message. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @description The ams. */
|
||||
ams?: components['schemas']['PrintAms'] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The ams rfid status.
|
||||
*/
|
||||
ams_rfid_status?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The ams status.
|
||||
*/
|
||||
ams_status?: number | null
|
||||
/** @description The aux part fan. */
|
||||
aux_part_fan?: boolean | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The target bed temperature.
|
||||
*/
|
||||
bed_target_temper?: number | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The bed temperature.
|
||||
*/
|
||||
bed_temper?: number | null
|
||||
/** @description The big fan 1 speed. */
|
||||
big_fan1_speed?: string | null
|
||||
/** @description The big fan 2 speed. */
|
||||
big_fan2_speed?: string | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The chamber temperature.
|
||||
*/
|
||||
chamber_temper?: number | null
|
||||
/** @enum {string} */
|
||||
command: 'push_status'
|
||||
/** @description The cooling fan speed. */
|
||||
cooling_fan_speed?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The fan gear.
|
||||
*/
|
||||
fan_gear?: number | null
|
||||
/** @description Force upgrade? */
|
||||
force_upgrade?: boolean | null
|
||||
/** @description The gcode file. */
|
||||
gcode_file?: string | null
|
||||
/** @description The gcode file prepare percent. */
|
||||
gcode_file_prepare_percent?: string | null
|
||||
/** @description The gcode state. */
|
||||
gcode_state?: string | null
|
||||
/** @description The heatbreak fan speed. */
|
||||
heatbreak_fan_speed?: string | null
|
||||
/** @description The hms. */
|
||||
hms?: unknown[] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The home flag.
|
||||
*/
|
||||
home_flag?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The hw switch state.
|
||||
*/
|
||||
hw_switch_state?: number | null
|
||||
/** @description The ipcam. */
|
||||
ipcam?: components['schemas']['PrintIpcam'] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The layer num.
|
||||
*/
|
||||
layer_num?: number | null
|
||||
/** @description The lifecycle. */
|
||||
lifecycle?: string | null
|
||||
/** @description The lights report. */
|
||||
lights_report?: components['schemas']['PrintLightsReport'][] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The percentage of the print completed.
|
||||
*/
|
||||
mc_percent?: number | null
|
||||
/** @description The mc print line number. */
|
||||
mc_print_line_number?: string | null
|
||||
/** @description The print stage. */
|
||||
mc_print_stage?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The mc print sub stage.
|
||||
*/
|
||||
mc_print_sub_stage?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The remaining time of the print.
|
||||
*/
|
||||
mc_remaining_time?: number | null
|
||||
/** @description The mess production state. */
|
||||
mess_production_state?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The message.
|
||||
*/
|
||||
msg?: number | null
|
||||
/** @description The nozzle diameter. */
|
||||
nozzle_diameter?: string | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The target nozzle temperature.
|
||||
*/
|
||||
nozzle_target_temper?: number | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The nozzle temperature.
|
||||
*/
|
||||
nozzle_temper?: number | null
|
||||
/** @description The nozzle type. */
|
||||
nozzle_type?: components['schemas']['NozzleType'] | null
|
||||
/** @description Online status. */
|
||||
online?: components['schemas']['PrintOnline'] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The print error.
|
||||
*/
|
||||
print_error?: number | null
|
||||
/** @description The print type. */
|
||||
print_type?: string | null
|
||||
/** @description The profile id. */
|
||||
profile_id?: string | null
|
||||
/** @description The project id. */
|
||||
project_id?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The queue est.
|
||||
*/
|
||||
queue_est?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The queue number.
|
||||
*/
|
||||
queue_number?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The queue sts.
|
||||
*/
|
||||
queue_sts?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The queue total.
|
||||
*/
|
||||
queue_total?: number | null
|
||||
/** @description The s obj. */
|
||||
s_obj?: unknown[] | null
|
||||
/** @description Sdcard? */
|
||||
sdcard?: boolean | null
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The spd lvl.
|
||||
*/
|
||||
spd_lvl?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The spd mag.
|
||||
*/
|
||||
spd_mag?: number | null
|
||||
/** @description The stg. */
|
||||
stg?: unknown[] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The stg cur.
|
||||
*/
|
||||
stg_cur?: number | null
|
||||
/** @description The subtask id. */
|
||||
subtask_id?: string | null
|
||||
/** @description The subtask name. */
|
||||
subtask_name?: string | null
|
||||
/** @description The task id. */
|
||||
task_id?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The total layer num.
|
||||
*/
|
||||
total_layer_num?: number | null
|
||||
/** @description The upgrade state. */
|
||||
upgrade_state?: components['schemas']['PrintUpgradeState'] | null
|
||||
/** @description The upload. */
|
||||
upload?: components['schemas']['PrintUpload'] | null
|
||||
/** @description The tray. */
|
||||
vt_tray?: components['schemas']['PrintTray'] | null
|
||||
/** @description The wifi signal. */
|
||||
wifi_signal?: string | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'gcode_line'
|
||||
/** @description The gcode line. */
|
||||
param?: string | null
|
||||
/** @description The reason for the message. */
|
||||
reason: components['schemas']['Reason']
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The return code. */
|
||||
return_code?: string | null
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The source.
|
||||
*/
|
||||
source?: number | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'gcode_file'
|
||||
/** @description The param. */
|
||||
param?: string | null
|
||||
/** @description The print type. */
|
||||
print_type?: string | null
|
||||
/** @description The reason for the message. */
|
||||
reason: components['schemas']['Reason']
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'project_file'
|
||||
/** @description The gcode file. */
|
||||
gcode_file?: string | null
|
||||
/** @description The profile id. */
|
||||
profile_id: string
|
||||
/** @description The project id. */
|
||||
project_id: string
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
/** @description The subtask id. */
|
||||
subtask_id: string
|
||||
/** @description The subtask name. */
|
||||
subtask_name: string
|
||||
/** @description The task id. */
|
||||
task_id: string
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'pause'
|
||||
/** @description The reason for the message. */
|
||||
reason: components['schemas']['Reason']
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'print_speed'
|
||||
/** @description The param. */
|
||||
param: string
|
||||
/** @description The reason for the message. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'resume'
|
||||
/** @description The reason for the message. */
|
||||
reason: components['schemas']['Reason']
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'stop'
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'extrusion_cali_get'
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
/** @description The print ams. */
|
||||
PrintAms: {
|
||||
/** @description The ams. */
|
||||
ams?: components['schemas']['PrintAmsData'][] | null
|
||||
/** @description The ams exist bits. */
|
||||
ams_exist_bits?: string | null
|
||||
/** @description The insert flag. */
|
||||
insert_flag?: boolean | null
|
||||
/** @description The power on flag. */
|
||||
power_on_flag?: boolean | null
|
||||
/** @description The tray exist bits. */
|
||||
tray_exist_bits?: string | null
|
||||
/** @description The tray is bbl bits. */
|
||||
tray_is_bbl_bits?: string | null
|
||||
/** @description The tray now. */
|
||||
tray_now?: string | null
|
||||
/** @description The tray pre. */
|
||||
tray_pre?: string | null
|
||||
/** @description The tray read done bits. */
|
||||
tray_read_done_bits?: string | null
|
||||
/** @description The tray reading bits. */
|
||||
tray_reading_bits?: string | null
|
||||
/** @description The tray tar. */
|
||||
tray_tar?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The version.
|
||||
*/
|
||||
version?: number | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The print ams data. */
|
||||
PrintAmsData: {
|
||||
/** @description The humidity. */
|
||||
humidity: string
|
||||
/** @description The id. */
|
||||
id: string
|
||||
/** @description The temperature. */
|
||||
temp: string
|
||||
/** @description The tray. */
|
||||
tray: components['schemas']['PrintTray'][]
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The print ipcam. */
|
||||
PrintIpcam: {
|
||||
/** @description The ipcam dev. */
|
||||
ipcam_dev?: string | null
|
||||
/** @description The ipcam record. */
|
||||
ipcam_record?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The mode bits.
|
||||
*/
|
||||
mode_bits?: number | null
|
||||
/** @description The timelapse. */
|
||||
timelapse?: string | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The response from the `/print` endpoint. */
|
||||
PrintJobResponse: {
|
||||
/** @description The job id used for this print. */
|
||||
@ -681,29 +150,6 @@ export interface components {
|
||||
/** @description The parameters used for this print. */
|
||||
parameters: components['schemas']['PrintParameters']
|
||||
}
|
||||
/** @description A print lights report. */
|
||||
PrintLightsReport: {
|
||||
/** @description The mode. */
|
||||
mode: components['schemas']['LedMode']
|
||||
/** @description The node. */
|
||||
node: components['schemas']['LedNode']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The print online. */
|
||||
PrintOnline: {
|
||||
/** @description The ahb. */
|
||||
ahb: boolean
|
||||
/** @description The rfid. */
|
||||
rfid?: boolean | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The version.
|
||||
*/
|
||||
version: number
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description Parameters for printing. */
|
||||
PrintParameters: {
|
||||
/** @description The name for the job. */
|
||||
@ -711,219 +157,26 @@ export interface components {
|
||||
/** @description The machine id to print to. */
|
||||
machine_id: string
|
||||
}
|
||||
/** @description The print tray. */
|
||||
PrintTray: {
|
||||
/** @description The bed temperature. */
|
||||
bed_temp?: string | null
|
||||
/** @description The bed temperature type. */
|
||||
bed_temp_type?: string | null
|
||||
/** @description The id. */
|
||||
id: string
|
||||
/** @description Set of three values to represent the extent of a 3-D Volume. This contains the width, depth, and height values, generally used to represent some maximum or minimum.
|
||||
*
|
||||
* All measurements are in millimeters. */
|
||||
Volume: {
|
||||
/**
|
||||
* Format: double
|
||||
* @description The tray k.
|
||||
* @description Depth of the volume ("front to back"), in millimeters.
|
||||
*/
|
||||
k?: number | null
|
||||
depth: number
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The tray n.
|
||||
* Format: double
|
||||
* @description Height of the volume ("up and down"), in millimeters.
|
||||
*/
|
||||
n?: number | null
|
||||
/** @description The nozzle temperature max. */
|
||||
nozzle_temp_max?: string | null
|
||||
/** @description The nozzle temperature min. */
|
||||
nozzle_temp_min?: string | null
|
||||
height: number
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The tray remain.
|
||||
* Format: double
|
||||
* @description Width of the volume ("left and right"), in millimeters.
|
||||
*/
|
||||
remain?: number | null
|
||||
/** @description The tag uid. */
|
||||
tag_uid?: string | null
|
||||
/** @description The tray color. */
|
||||
tray_color?: string | null
|
||||
/** @description The tray diameter. */
|
||||
tray_diameter?: string | null
|
||||
/** @description The tray id name. */
|
||||
tray_id_name?: string | null
|
||||
/** @description The tray info index. */
|
||||
tray_info_idx?: string | null
|
||||
/** @description The tray sub brands. */
|
||||
tray_sub_brands?: string | null
|
||||
/** @description The tray temperature. */
|
||||
tray_temp?: string | null
|
||||
/** @description The tray time. */
|
||||
tray_time?: string | null
|
||||
/** @description The tray type. */
|
||||
tray_type?: string | null
|
||||
/** @description The tray uuid. */
|
||||
tray_uuid?: string | null
|
||||
/** @description The tray weight. */
|
||||
tray_weight?: string | null
|
||||
/** @description The xcam info. */
|
||||
xcam_info?: string | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
width: number
|
||||
}
|
||||
/** @description A print upgrade state. */
|
||||
PrintUpgradeState: {
|
||||
/** @description The consistency request. */
|
||||
consistency_request?: boolean | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The dis state.
|
||||
*/
|
||||
dis_state?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The error code.
|
||||
*/
|
||||
err_code?: number | null
|
||||
/** @description Force upgrade? */
|
||||
force_upgrade?: boolean | null
|
||||
/** @description The message. */
|
||||
message?: string | null
|
||||
/** @description The module. */
|
||||
module?: string | null
|
||||
/** @description The new version list. */
|
||||
new_ver_list?: unknown[] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The new version state.
|
||||
*/
|
||||
new_version_state?: number | null
|
||||
/** @description The progress. */
|
||||
progress?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The sequence id.
|
||||
*/
|
||||
sequence_id?: number | null
|
||||
/** @description The status. */
|
||||
status?: string | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The print upload. */
|
||||
PrintUpload: {
|
||||
/** @description The message. */
|
||||
message: string
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The progress.
|
||||
*/
|
||||
progress: number
|
||||
/** @description The status. */
|
||||
status: string
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description A reason for a message. */
|
||||
Reason:
|
||||
| 'SUCCESS'
|
||||
| 'FAIL'
|
||||
| {
|
||||
UNKNOWN: string
|
||||
}
|
||||
/** @description The result of a message. */
|
||||
Result: 'SUCCESS' | 'FAIL'
|
||||
/** @description A security message. */
|
||||
Security: {
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The address.
|
||||
*/
|
||||
address: number
|
||||
/** @description The chip sn. */
|
||||
chip_sn: string
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The chip sn length.
|
||||
*/
|
||||
chipsn_len: number
|
||||
/** @enum {string} */
|
||||
command: 'get_sn'
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The length.
|
||||
*/
|
||||
length: number
|
||||
/** @description The module. */
|
||||
module: string
|
||||
/** @description The reason for the message. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
/** @description The serial number. */
|
||||
sn: string
|
||||
/** @description The status. */
|
||||
status: string
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The sequence id type. */
|
||||
SequenceId: string | number
|
||||
/** @description A system command. */
|
||||
System:
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'ledctrl'
|
||||
/**
|
||||
* Format: uint32
|
||||
* @description The interval time.
|
||||
*/
|
||||
interval_time: number
|
||||
/** @description The LED mode. */
|
||||
led_mode: components['schemas']['LedMode']
|
||||
/** @description The LED node. */
|
||||
led_node: components['schemas']['LedNode']
|
||||
/**
|
||||
* Format: uint32
|
||||
* @description The LED off time.
|
||||
*/
|
||||
led_off_time: number
|
||||
/**
|
||||
* Format: uint32
|
||||
* @description The LED on time.
|
||||
*/
|
||||
led_on_time: number
|
||||
/**
|
||||
* Format: uint32
|
||||
* @description The loop times.
|
||||
*/
|
||||
loop_times: number
|
||||
/** @description The reason for the message. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @description The accessory type. */
|
||||
accessory_type: components['schemas']['AccessoryType']
|
||||
/** @description The aux part fan. */
|
||||
aux_part_fan: boolean
|
||||
/** @enum {string} */
|
||||
command: 'get_accessories'
|
||||
/**
|
||||
* Format: double
|
||||
* @description The nozzle diameter.
|
||||
*/
|
||||
nozzle_diameter: number
|
||||
/** @description The nozzle type. */
|
||||
nozzle_type: components['schemas']['NozzleType']
|
||||
/** @description The reason for the message. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
}
|
||||
responses: {
|
||||
/** @description Error */
|
||||
@ -980,9 +233,7 @@ export interface operations {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': {
|
||||
[key: string]: components['schemas']['Machine']
|
||||
}
|
||||
'application/json': components['schemas']['MachineInfoResponse'][]
|
||||
}
|
||||
}
|
||||
'4XX': components['responses']['Error']
|
||||
@ -1007,7 +258,7 @@ export interface operations {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['Message']
|
||||
'application/json': components['schemas']['MachineInfoResponse']
|
||||
}
|
||||
}
|
||||
'4XX': components['responses']['Error']
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { isDesktop } from './isDesktop'
|
||||
import { components } from './machine-api'
|
||||
|
||||
export type MachinesListing = {
|
||||
[key: string]: components['schemas']['Machine']
|
||||
}
|
||||
export type MachinesListing = Array<
|
||||
components['schemas']['MachineInfoResponse']
|
||||
>
|
||||
|
||||
export class MachineManager {
|
||||
private _isDesktop: boolean = isDesktop()
|
||||
private _machines: MachinesListing = {}
|
||||
private _machines: MachinesListing = []
|
||||
private _machineApiIp: string | null = null
|
||||
private _currentMachine: components['schemas']['Machine'] | null = null
|
||||
private _currentMachine: components['schemas']['MachineInfoResponse'] | null =
|
||||
null
|
||||
|
||||
constructor() {
|
||||
if (!this._isDesktop) {
|
||||
@ -44,7 +45,7 @@ export class MachineManager {
|
||||
}
|
||||
|
||||
machineCount(): number {
|
||||
return Object.keys(this._machines).length
|
||||
return this._machines.length
|
||||
}
|
||||
|
||||
get machineApiIp(): string | null {
|
||||
@ -64,11 +65,13 @@ export class MachineManager {
|
||||
return 'Machine API server was discovered, but no machines are available'
|
||||
}
|
||||
|
||||
get currentMachine(): components['schemas']['Machine'] | null {
|
||||
get currentMachine(): components['schemas']['MachineInfoResponse'] | null {
|
||||
return this._currentMachine
|
||||
}
|
||||
|
||||
set currentMachine(machine: components['schemas']['Machine'] | null) {
|
||||
set currentMachine(
|
||||
machine: components['schemas']['MachineInfoResponse'] | null
|
||||
) {
|
||||
this._currentMachine = machine
|
||||
}
|
||||
|
||||
|
@ -90,12 +90,24 @@ export const fileLoader: LoaderFunction = async (
|
||||
let code = ''
|
||||
|
||||
if (!urlObj.pathname.endsWith('/settings')) {
|
||||
if (!currentFileName || !currentFilePath || !projectName) {
|
||||
const fallbackFile = isDesktop()
|
||||
? (await getProjectInfo(projectPath)).default_file
|
||||
: ''
|
||||
let fileExists = isDesktop()
|
||||
if (currentFilePath && fileExists) {
|
||||
try {
|
||||
await window.electron.stat(currentFilePath)
|
||||
} catch (e) {
|
||||
if (e === 'ENOENT') {
|
||||
fileExists = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileExists || !currentFileName || !currentFilePath || !projectName) {
|
||||
return redirect(
|
||||
`${PATHS.FILE}/${encodeURIComponent(
|
||||
isDesktop()
|
||||
? (await getProjectInfo(projectPath)).default_file
|
||||
: params.id + '/' + PROJECT_ENTRYPOINT
|
||||
isDesktop() ? fallbackFile : params.id + '/' + PROJECT_ENTRYPOINT
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
getArtifactOfTypes,
|
||||
getArtifactsOfTypes,
|
||||
getCapCodeRef,
|
||||
getExtrudeEdgeCodeRef,
|
||||
getSolid2dCodeRef,
|
||||
getWallCodeRef,
|
||||
} from 'lang/std/artifactGraph'
|
||||
@ -141,6 +142,20 @@ export async function getEventForSelectWithPoint({
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'extrudeEdge') {
|
||||
const codeRef = getExtrudeEdgeCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'edge' },
|
||||
},
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ window.tearDown = engineCommandManager.tearDown
|
||||
// This needs to be after codeManager is created.
|
||||
export const kclManager = new KclManager(engineCommandManager)
|
||||
kclManager.isFirstRender = true
|
||||
engineCommandManager.kclManager = kclManager
|
||||
|
||||
engineCommandManager.getAstCb = () => kclManager.ast
|
||||
|
||||
|
@ -1118,13 +1118,11 @@ export const modelingMachine = createMachine(
|
||||
store.videoElement?.pause()
|
||||
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||
focusPath: pathToExtrudeArg,
|
||||
// commented out as a part of https://github.com/KittyCAD/modeling-app/issues/3270
|
||||
// looking to add back in the future
|
||||
// zoomToFit: true,
|
||||
// zoomOnRangeAndType: {
|
||||
// range: selection.codeBasedSelections[0].range,
|
||||
// type: 'path',
|
||||
// },
|
||||
zoomToFit: true,
|
||||
zoomOnRangeAndType: {
|
||||
range: selection.codeBasedSelections[0].range,
|
||||
type: 'path',
|
||||
},
|
||||
})
|
||||
if (!engineCommandManager.engineConnection?.idleMode) {
|
||||
store.videoElement?.play().catch((e) => {
|
||||
|
46
src/main.ts
@ -8,8 +8,10 @@ import { Issuer } from 'openid-client'
|
||||
import { Bonjour, Service } from 'bonjour-service'
|
||||
// @ts-ignore: TS1343
|
||||
import * as kittycad from '@kittycad/lib/import'
|
||||
import electronUpdater, { type AppUpdater } from 'electron-updater'
|
||||
import minimist from 'minimist'
|
||||
import getCurrentProjectFile from 'lib/getCurrentProjectFile'
|
||||
import os from 'node:os'
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
@ -22,8 +24,20 @@ if (!process.env.NODE_ENV)
|
||||
console.warn(
|
||||
'*FOX SCREAM* process.env.NODE_ENV is not explicitly set!, defaulting to production'
|
||||
)
|
||||
// Default prod values
|
||||
|
||||
// dotenv override when present
|
||||
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
|
||||
|
||||
console.log(process.env)
|
||||
|
||||
process.env.VITE_KC_API_WS_MODELING_URL ??=
|
||||
'wss://api.zoo.dev/ws/modeling/commands'
|
||||
process.env.VITE_KC_API_BASE_URL ??= 'https://api.zoo.dev'
|
||||
process.env.VITE_KC_SITE_BASE_URL ??= 'https://zoo.dev'
|
||||
process.env.VITE_KC_SKIP_AUTH ??= 'false'
|
||||
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??= '15000'
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (require('electron-squirrel-startup')) {
|
||||
app.quit()
|
||||
@ -59,7 +73,7 @@ const createWindow = (): BrowserWindow => {
|
||||
preload: path.join(__dirname, './preload.js'),
|
||||
},
|
||||
icon: path.resolve(process.cwd(), 'assets', 'icon.png'),
|
||||
frame: false,
|
||||
frame: os.platform() !== 'darwin',
|
||||
titleBarStyle: 'hiddenInset',
|
||||
})
|
||||
|
||||
@ -191,6 +205,36 @@ ipcMain.handle('find_machine_api', () => {
|
||||
})
|
||||
})
|
||||
|
||||
export function getAutoUpdater(): AppUpdater {
|
||||
// Using destructuring to access autoUpdater due to the CommonJS module of 'electron-updater'.
|
||||
// It is a workaround for ESM compatibility issues, see https://github.com/electron-userland/electron-builder/issues/7976.
|
||||
const { autoUpdater } = electronUpdater
|
||||
return autoUpdater
|
||||
}
|
||||
|
||||
export async function checkForUpdates(autoUpdater: AppUpdater) {
|
||||
// TODO: figure out how to get the update modal back
|
||||
const result = await autoUpdater.checkForUpdatesAndNotify()
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
app.on('ready', async () => {
|
||||
const autoUpdater = getAutoUpdater()
|
||||
checkForUpdates(autoUpdater)
|
||||
const fifteenMinutes = 15 * 60 * 1000
|
||||
setInterval(() => {
|
||||
checkForUpdates(autoUpdater)
|
||||
}, fifteenMinutes)
|
||||
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
console.log('update-available', info)
|
||||
})
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
console.log('update-downloaded', info)
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.handle('loadProjectAtStartup', async () => {
|
||||
// If we are in development mode, we don't want to load a project at
|
||||
// startup.
|
||||
|
@ -52,7 +52,7 @@ const kittycad = (access: string, args: any) =>
|
||||
// bite our butts.
|
||||
const listMachines = async (): Promise<MachinesListing> => {
|
||||
const machineApi = await ipcRenderer.invoke('find_machine_api')
|
||||
if (!machineApi) return {}
|
||||
if (!machineApi) return []
|
||||
|
||||
return fetch(`http://${machineApi}/machines`).then((resp) => resp.json())
|
||||
}
|
||||
|
@ -5,6 +5,16 @@ import { Themes, getSystemTheme } from '../lib/theme'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import { CSSProperties, useCallback } from 'react'
|
||||
import { Logo } from 'components/Logo'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { APP_VERSION } from './Settings'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
|
||||
const subtleBorder =
|
||||
'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
|
||||
const cardArea = `${subtleBorder} rounded-lg px-6 py-3 text-chalkboard-70 dark:text-chalkboard-30`
|
||||
|
||||
const SignIn = () => {
|
||||
const {
|
||||
@ -17,12 +27,25 @@ const SignIn = () => {
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
const signInUrl = `${VITE_KC_SITE_BASE_URL}${
|
||||
PATHS.SIGN_IN
|
||||
}?callbackUrl=${encodeURIComponent(
|
||||
typeof window !== 'undefined' && window.location.href.replace('signin', '')
|
||||
)}`
|
||||
const kclSampleUrl = `${VITE_KC_SITE_BASE_URL}/docs/kcl-samples/car-wheel`
|
||||
|
||||
const getLogoTheme = () =>
|
||||
theme.current === Themes.Light ||
|
||||
(theme.current === Themes.System && getSystemTheme() === Themes.Light)
|
||||
? '-dark'
|
||||
: ''
|
||||
const getThemeText = useCallback(
|
||||
(shouldContrast = true) =>
|
||||
theme.current === Themes.Light ||
|
||||
(theme.current === Themes.System && getSystemTheme() === Themes.Light)
|
||||
? shouldContrast
|
||||
? '-dark'
|
||||
: ''
|
||||
: shouldContrast
|
||||
? ''
|
||||
: '-dark',
|
||||
[theme.current]
|
||||
)
|
||||
|
||||
const signInDesktop = async () => {
|
||||
// We want to invoke our command to login via device auth.
|
||||
@ -35,56 +58,192 @@ const SignIn = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="body-bg h-full min-h-screen m-0 p-0 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div>
|
||||
<img
|
||||
src={`./zma-logomark${getLogoTheme()}.svg`}
|
||||
alt="Zoo Modeling App"
|
||||
className="w-48 inline-block"
|
||||
/>
|
||||
<main className="bg-primary h-screen grid place-items-stretch m-0 p-2">
|
||||
<div
|
||||
style={
|
||||
{
|
||||
height: 'calc(100vh - 16px)',
|
||||
'--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"
|
||||
>
|
||||
<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="flex items-baseline mb-8">
|
||||
<Logo className="text-primary h-10 lg:h-12 xl:h-16 relative translate-y-1 mr-4 lg:mr-6 xl:mr-8" />
|
||||
<h1 className="text-3xl lg:text-4xl xl:text-5xl">{APP_NAME}</h1>
|
||||
<span className="px-3 py-1 text-base rounded-full bg-primary/10 text-primary self-start">
|
||||
alpha v{APP_VERSION}
|
||||
</span>
|
||||
</div>
|
||||
<p className="my-4 text-lg xl:text-xl">
|
||||
Thank you for using our hardware design application. It is built
|
||||
on a novel CAD engine and crafted to help you create parametric,
|
||||
version-controlled, and accurate parts ready for manufacturing.
|
||||
</p>
|
||||
<p className="my-4 text-lg xl:text-xl">
|
||||
As alpha software, Zoo Modeling App is still in heavy development.
|
||||
We encourage feedback and feature requests that align with{' '}
|
||||
<a
|
||||
href="https://github.com/KittyCAD/modeling-app/issues/729"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
our roadmap to v1.0
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
{isDesktop() ? (
|
||||
<button
|
||||
onClick={signInDesktop}
|
||||
className={
|
||||
'm-0 mt-8 flex gap-4 items-center px-3 py-1 ' +
|
||||
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
|
||||
}
|
||||
data-testid="sign-in-button"
|
||||
>
|
||||
Sign in to get started
|
||||
<CustomIcon name="arrowRight" className="w-6 h-6" />
|
||||
</button>
|
||||
) : (
|
||||
<Link
|
||||
onClick={openExternalBrowserIfDesktop(signInUrl)}
|
||||
to={signInUrl}
|
||||
className={
|
||||
'w-fit m-0 mt-8 flex gap-4 items-center px-3 py-1 ' +
|
||||
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
|
||||
}
|
||||
data-testid="sign-in-button"
|
||||
>
|
||||
Sign in to get started
|
||||
<CustomIcon name="arrowRight" className="w-6 h-6" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
className={`group relative xl:h-full xl:row-span-full col-start--1 xl:col-start-4 rounded-lg overflow-hidden grid place-items-center ${subtleBorder}`}
|
||||
to={kclSampleUrl}
|
||||
onClick={openExternalBrowserIfDesktop(kclSampleUrl)}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<video
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="h-full object-cover object-center"
|
||||
>
|
||||
<source
|
||||
src={`${isDesktop() ? '.' : ''}/wheel-loop${getThemeText(
|
||||
false
|
||||
)}.mp4`}
|
||||
type="video/mp4"
|
||||
/>
|
||||
</video>
|
||||
<div
|
||||
className={
|
||||
'absolute bottom-0 left-0 right-0 transition translate-y-4 opacity-0 ' +
|
||||
'group-hover:translate-y-0 group-hover:opacity-100 ' +
|
||||
'm-0 mt-8 flex gap-4 items-center px-3 py-1 ' +
|
||||
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
|
||||
}
|
||||
data-testid="sign-in-button"
|
||||
>
|
||||
View this sample
|
||||
<CustomIcon name="arrowRight" className="w-6 h-6" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="self-end h-min col-span-3 xl:row-span-2 grid grid-cols-2 gap-5">
|
||||
<div className={cardArea}>
|
||||
<h2 className="text-xl">Built in the open</h2>
|
||||
<p className="text-xs my-4">
|
||||
Open-source and open discussions. Check our public code base and
|
||||
join our Discord.
|
||||
</p>
|
||||
<div className="flex gap-4 flex-wrap items-center">
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
to="https://github.com/KittyCAD/modeling-app"
|
||||
iconStart={{ icon: 'code' }}
|
||||
className="border-chalkboard-30 dark:border-chalkboard-80"
|
||||
>
|
||||
<span className="py-2 lg:py-0">Read our source code</span>
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
to="https://discord.gg/JQEpHR7Nt2"
|
||||
iconStart={{ icon: 'keyboard' }}
|
||||
className="border-chalkboard-30 dark:border-chalkboard-80"
|
||||
>
|
||||
<span className="py-2 lg:py-0">Join our community</span>
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cardArea}>
|
||||
<h2 className="text-xl">Ready for the future</h2>
|
||||
<p className="text-xs my-4">
|
||||
Modern software ideas being brought together to create a
|
||||
familiar modeling experience with new superpowers.
|
||||
</p>
|
||||
<div className="flex gap-4 flex-wrap items-center">
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
to="https://zoo.dev/docs/kcl-samples/ball-bearing"
|
||||
iconStart={{ icon: 'settings' }}
|
||||
className="border-chalkboard-30 dark:border-chalkboard-80"
|
||||
>
|
||||
<span className="py-2 lg:py-0">
|
||||
Parametric design with KCL
|
||||
</span>
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
to="https://zoo.dev/docs/tutorials/text-to-cad"
|
||||
iconStart={{ icon: 'sparkles' }}
|
||||
className="border-chalkboard-30 dark:border-chalkboard-80"
|
||||
>
|
||||
<span className="py-2 lg:py-0">AI-unlocked CAD</span>
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cardArea + ' col-span-2'}>
|
||||
<h2 className="text-xl">
|
||||
Built on the first infrastructure for hardware design
|
||||
</h2>
|
||||
<p className="text-xs my-4">
|
||||
You can make your own niche hardware design tools with our
|
||||
design and machine learning interfaces. We're building Modeling
|
||||
App in the same way.
|
||||
</p>
|
||||
<div className="flex gap-4 flex-wrap items-center">
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
to="https://zoo.dev/design-api"
|
||||
iconStart={{ icon: 'sketch' }}
|
||||
className="border-chalkboard-30 dark:border-chalkboard-80"
|
||||
>
|
||||
<span className="py-2 lg:py-0">KittyCAD Design API</span>
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
to="https://zoo.dev/machine-learning-api"
|
||||
iconStart={{ icon: 'elephant' }}
|
||||
className="border-chalkboard-30 dark:border-chalkboard-80"
|
||||
>
|
||||
<span className="py-2 lg:py-0">
|
||||
ML-ephant Machine Learning API
|
||||
</span>
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="font-bold text-2xl mt-12 mb-6">
|
||||
Sign in to get started with the {APP_NAME}
|
||||
</h1>
|
||||
<p className="py-4">
|
||||
ZMA is an open-source CAD application for creating accurate 3D models
|
||||
for use in manufacturing. It is built on top of KittyCAD, the design
|
||||
API from Zoo. Zoo is the first software infrastructure company built
|
||||
specifically for the needs of the manufacturing industry. With ZMA we
|
||||
are showing how the KittyCAD API from Zoo can be used to build
|
||||
entirely new kinds of software for manufacturing.
|
||||
</p>
|
||||
<p className="py-4">
|
||||
ZMA is currently in development. If you would like to be notified when
|
||||
ZMA is ready for production, please sign up for our mailing list at{' '}
|
||||
<a href="https://zoo.dev">zoo.dev</a>.
|
||||
</p>
|
||||
{isDesktop() ? (
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={signInDesktop}
|
||||
iconStart={{ icon: 'arrowRight' }}
|
||||
className="w-fit mt-4"
|
||||
data-testid="sign-in-button"
|
||||
>
|
||||
Sign in
|
||||
</ActionButton>
|
||||
) : (
|
||||
<ActionButton
|
||||
Element="link"
|
||||
to={`${VITE_KC_SITE_BASE_URL}${
|
||||
PATHS.SIGN_IN
|
||||
}?callbackUrl=${encodeURIComponent(
|
||||
typeof window !== 'undefined' &&
|
||||
window.location.href.replace('signin', '')
|
||||
)}`}
|
||||
iconStart={{ icon: 'arrowRight' }}
|
||||
className="w-fit mt-4"
|
||||
>
|
||||
Sign in
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
|
84
src/wasm-lib/Cargo.lock
generated
@ -127,18 +127,18 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.81"
|
||||
version = "0.1.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
||||
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -149,7 +149,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -397,7 +397,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -591,7 +591,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -602,7 +602,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -657,7 +657,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -686,7 +686,7 @@ dependencies = [
|
||||
"rustfmt-wrapper",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -697,7 +697,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -724,7 +724,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -896,7 +896,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -986,7 +986,7 @@ dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1412,7 +1412,7 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1430,9 +1430,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.3.17"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbb7c076d64ad00a29ae900108707d1bbb583944d4b2d005e1eca9914a18c7c2"
|
||||
checksum = "94feea5b1cf851b33dd108aa35aa01bde99772aa74d2ba1590295aac0b7ca33e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1799,7 +1799,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.3",
|
||||
"structmeta",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1852,7 +1852,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2016,7 +2016,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2029,7 +2029,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2491,7 +2491,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2565,7 +2565,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2576,7 +2576,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2600,7 +2600,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2621,7 +2621,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2752,7 +2752,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2763,7 +2763,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2807,9 +2807,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.76"
|
||||
version = "2.0.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2830,7 +2830,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2937,7 +2937,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3008,9 +3008,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.39.3"
|
||||
version = "1.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
|
||||
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@ -3032,7 +3032,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3185,7 +3185,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3213,7 +3213,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3290,7 +3290,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -3448,7 +3448,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3509,7 +3509,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -3544,7 +3544,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -3869,7 +3869,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.76",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -16,7 +16,7 @@ gloo-utils = "0.2.0"
|
||||
kcl-lib = { path = "kcl" }
|
||||
kittycad.workspace = true
|
||||
serde_json = "1.0.127"
|
||||
tokio = { version = "1.39.3", features = ["sync"] }
|
||||
tokio = { version = "1.40.0", features = ["sync"] }
|
||||
toml = "0.8.19"
|
||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
||||
wasm-bindgen = "0.2.91"
|
||||
@ -29,7 +29,7 @@ image = { version = "0.25.1", default-features = false, features = ["png"] }
|
||||
kittycad = { workspace = true, default-features = true }
|
||||
pretty_assertions = "1.4.0"
|
||||
reqwest = { version = "0.11.26", default-features = false }
|
||||
tokio = { version = "1.39.3", features = ["rt-multi-thread", "macros", "time"] }
|
||||
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.8"
|
||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
||||
|
||||
@ -70,7 +70,7 @@ members = [
|
||||
|
||||
[workspace.dependencies]
|
||||
http = "0.2.12"
|
||||
kittycad = { version = "0.3.17", default-features = false, features = ["js", "requests"] }
|
||||
kittycad = { version = "0.3.18", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-modeling-session = "0.1.4"
|
||||
|
||||
[[test]]
|
||||
|
@ -20,7 +20,7 @@ quote = "1"
|
||||
regex = "1.10"
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.76", features = ["full"] }
|
||||
syn = { version = "2.0.77", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.86"
|
||||
|
@ -15,7 +15,7 @@ databake = "0.1.8"
|
||||
kcl-lib = { path = "../kcl" }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2.0.76", features = ["full"] }
|
||||
syn = { version = "2.0.77", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
@ -12,4 +12,4 @@ kcl-lib = { version = "0.2", path = "../kcl" }
|
||||
pico-args = "0.5.0"
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
serde_json = "1.0.127"
|
||||
tokio = { version = "1.39.3", features = ["macros", "rt-multi-thread"] }
|
||||
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
|
||||
|
@ -13,7 +13,7 @@ keywords = ["kcl", "KittyCAD", "CAD"]
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.86", features = ["backtrace"] }
|
||||
async-recursion = "1.1.1"
|
||||
async-trait = "0.1.81"
|
||||
async-trait = "0.1.82"
|
||||
base64 = "0.22.1"
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.5.16", default-features = false, optional = true, features = ["std", "derive"] }
|
||||
@ -52,7 +52,7 @@ zip = { version = "2.0.0", default-features = false }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
js-sys = { version = "0.3.69" }
|
||||
tokio = { version = "1.39.3", features = ["sync", "time"] }
|
||||
tokio = { version = "1.40.0", features = ["sync", "time"] }
|
||||
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
||||
wasm-bindgen = "0.2.91"
|
||||
wasm-bindgen-futures = "0.4.42"
|
||||
@ -94,7 +94,7 @@ image = { version = "0.25.1", default-features = false, features = ["png"] }
|
||||
insta = { version = "1.38.0", features = ["json"] }
|
||||
itertools = "0.13.0"
|
||||
pretty_assertions = "1.4.0"
|
||||
tokio = { version = "1.39.2", features = ["rt-multi-thread", "macros", "time"] }
|
||||
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.8.0"
|
||||
|
||||
[[bench]]
|
||||
|
@ -2573,7 +2573,6 @@ impl ObjectExpression {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
dbg!(&format_items);
|
||||
let end_indent = if is_in_pipe {
|
||||
options.get_indentation_offset_pipe(indentation_level)
|
||||
} else {
|
||||
|
@ -9,6 +9,8 @@ use crate::{
|
||||
executor::{KclValue, SourceRange, UserVal},
|
||||
};
|
||||
|
||||
const KCL_NONE_ID: &str = "KCL_NONE_ID";
|
||||
|
||||
/// KCL value for an optional parameter which was not given an argument.
|
||||
/// (remember, parameters are in the function declaration,
|
||||
/// arguments are in the function call/application).
|
||||
@ -20,6 +22,45 @@ pub struct KclNone {
|
||||
// TODO: Convert this to be an Option<SourceRange>.
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
#[serde(deserialize_with = "deser_private")]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
__private: Private,
|
||||
}
|
||||
|
||||
impl KclNone {
|
||||
pub fn new(start: usize, end: usize) -> Self {
|
||||
Self {
|
||||
start,
|
||||
end,
|
||||
__private: Private {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Bake, Default)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
struct Private;
|
||||
|
||||
impl Serialize for Private {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(KCL_NONE_ID)
|
||||
}
|
||||
}
|
||||
|
||||
fn deser_private<'de, D>(deserializer: D) -> Result<Private, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if s == KCL_NONE_ID {
|
||||
Ok(Private {})
|
||||
} else {
|
||||
Err(serde::de::Error::custom("not a KCL none"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KclNone> for SourceRange {
|
||||
@ -57,3 +98,24 @@ impl KclNone {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn other_types_will_not_deserialize() {
|
||||
// This shouldn't deserialize into a KCL None,
|
||||
// because it's missing the special Private tag.
|
||||
let j = r#"{"start": 0, "end": 0}"#;
|
||||
let _e = serde_json::from_str::<KclNone>(j).unwrap_err();
|
||||
}
|
||||
#[test]
|
||||
fn serialize_then_deserialize() {
|
||||
// Serializing, then deserializing a None should produce the same value.
|
||||
let before = KclNone::default();
|
||||
let j = serde_json::to_string_pretty(&before).unwrap();
|
||||
let after: KclNone = serde_json::from_str(&j).unwrap();
|
||||
assert_eq!(before, after);
|
||||
}
|
||||
}
|
||||
|
@ -2080,10 +2080,7 @@ fn assign_args_to_params(
|
||||
if param.optional {
|
||||
// If the corresponding parameter is optional,
|
||||
// then it's fine, the user doesn't need to supply it.
|
||||
let none = KclNone {
|
||||
start: param.identifier.start,
|
||||
end: param.identifier.end,
|
||||
};
|
||||
let none = KclNone::new(param.identifier.start, param.identifier.end);
|
||||
fn_memory.add(
|
||||
¶m.identifier.name,
|
||||
KclValue::from(&none),
|
||||
|
@ -135,13 +135,16 @@ fn non_code_node_no_leading_whitespace(i: TokenSlice) -> PResult<NonCodeNode> {
|
||||
|
||||
fn pipe_expression(i: TokenSlice) -> PResult<PipeExpression> {
|
||||
let mut non_code_meta = NonCodeMeta::default();
|
||||
let (head, noncode) = terminated(
|
||||
(expression_but_not_pipe, preceded(whitespace, opt(non_code_node))),
|
||||
let (head, noncode): (_, Vec<_>) = terminated(
|
||||
(
|
||||
expression_but_not_pipe,
|
||||
repeat(0.., preceded(whitespace, non_code_node)),
|
||||
),
|
||||
peek(pipe_surrounded_by_whitespace),
|
||||
)
|
||||
.context(expected("an expression, followed by the |> (pipe) operator"))
|
||||
.parse_next(i)?;
|
||||
if let Some(nc) = noncode {
|
||||
for nc in noncode {
|
||||
non_code_meta.insert(0, nc);
|
||||
}
|
||||
let mut values = vec![head];
|
||||
@ -3453,6 +3456,17 @@ mod snapshot_tests {
|
||||
c: 3
|
||||
}"
|
||||
);
|
||||
snapshot_test!(
|
||||
ba,
|
||||
r#"
|
||||
const sketch001 = startSketchOn('XY')
|
||||
// |> arc({
|
||||
// angleEnd: 270,
|
||||
// angleStart: 450,
|
||||
// }, %)
|
||||
|> startProfileAt(%)
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -0,0 +1,150 @@
|
||||
---
|
||||
source: kcl/src/parser/parser_impl.rs
|
||||
expression: actual
|
||||
---
|
||||
{
|
||||
"start": 0,
|
||||
"end": 133,
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 1,
|
||||
"end": 132,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 7,
|
||||
"end": 132,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 7,
|
||||
"end": 16,
|
||||
"name": "sketch001",
|
||||
"digest": null
|
||||
},
|
||||
"init": {
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression",
|
||||
"start": 19,
|
||||
"end": 132,
|
||||
"body": [
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 19,
|
||||
"end": 38,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 19,
|
||||
"end": 32,
|
||||
"name": "startSketchOn",
|
||||
"digest": null
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 33,
|
||||
"end": 37,
|
||||
"value": "XY",
|
||||
"raw": "'XY'",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"optional": false,
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 115,
|
||||
"end": 132,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 115,
|
||||
"end": 129,
|
||||
"name": "startProfileAt",
|
||||
"digest": null
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 130,
|
||||
"end": 131,
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"optional": false,
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {
|
||||
"0": [
|
||||
{
|
||||
"type": "NonCodeNode",
|
||||
"start": 41,
|
||||
"end": 52,
|
||||
"value": {
|
||||
"type": "blockComment",
|
||||
"value": "|> arc({",
|
||||
"style": "line"
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "NonCodeNode",
|
||||
"start": 55,
|
||||
"end": 74,
|
||||
"value": {
|
||||
"type": "blockComment",
|
||||
"value": "angleEnd: 270,",
|
||||
"style": "line"
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "NonCodeNode",
|
||||
"start": 77,
|
||||
"end": 98,
|
||||
"value": {
|
||||
"type": "blockComment",
|
||||
"value": "angleStart: 450,",
|
||||
"style": "line"
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "NonCodeNode",
|
||||
"start": 101,
|
||||
"end": 109,
|
||||
"value": {
|
||||
"type": "blockComment",
|
||||
"value": "}, %)",
|
||||
"style": "line"
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"kind": "const",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
@ -495,6 +495,9 @@ where
|
||||
{
|
||||
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
|
||||
let Some(arg) = args.args.get(i) else { return Ok(None) };
|
||||
if crate::ast::types::KclNone::from_mem_item(arg).is_some() {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(val) = T::from_mem_item(arg) else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
@ -620,6 +623,7 @@ impl_from_arg_via_json!(crate::std::polar::PolarCoordsData);
|
||||
impl_from_arg_via_json!(SketchGroup);
|
||||
impl_from_arg_via_json!(FaceTag);
|
||||
impl_from_arg_via_json!(String);
|
||||
impl_from_arg_via_json!(crate::ast::types::KclNone);
|
||||
impl_from_arg_via_json!(u32);
|
||||
impl_from_arg_via_json!(u64);
|
||||
impl_from_arg_via_json!(f64);
|
||||
|
@ -181,6 +181,32 @@ pub(crate) async fn do_post_extrude(
|
||||
vec![]
|
||||
};
|
||||
|
||||
for face_info in face_infos.iter() {
|
||||
if face_info.cap == kittycad::types::ExtrusionFaceCapType::None {
|
||||
if let (Some(curve_id), Some(face_id)) = (face_info.curve_id, face_info.face_id) {
|
||||
args.batch_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
kittycad::types::ModelingCmd::Solid3DGetOppositeEdge {
|
||||
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?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a hashmap for quick id lookup
|
||||
let mut face_id_map = std::collections::HashMap::new();
|
||||
// creating fake ids for start and end caps is to make extrudes mock-execute safe
|
||||
|
@ -304,7 +304,7 @@ async fn inner_get_next_adjacent_edge(tag: TagIdentifier, args: Args) -> Result<
|
||||
let resp = args
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
ModelingCmd::Solid3DGetPrevAdjacentEdge {
|
||||
ModelingCmd::Solid3DGetNextAdjacentEdge {
|
||||
edge_id: tagged_path.id,
|
||||
object_id: tagged_path.sketch_group,
|
||||
face_id,
|
||||
@ -312,7 +312,7 @@ async fn inner_get_next_adjacent_edge(tag: TagIdentifier, args: Args) -> Result<
|
||||
)
|
||||
.await?;
|
||||
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetPrevAdjacentEdge { data: ajacent_edge },
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetNextAdjacentEdge { data: ajacent_edge },
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
@ -386,7 +386,7 @@ async fn inner_get_previous_adjacent_edge(tag: TagIdentifier, args: Args) -> Res
|
||||
let resp = args
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
ModelingCmd::Solid3DGetNextAdjacentEdge {
|
||||
ModelingCmd::Solid3DGetPrevAdjacentEdge {
|
||||
edge_id: tagged_path.id,
|
||||
object_id: tagged_path.sketch_group,
|
||||
face_id,
|
||||
@ -394,7 +394,7 @@ async fn inner_get_previous_adjacent_edge(tag: TagIdentifier, args: Args) -> Res
|
||||
)
|
||||
.await?;
|
||||
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetNextAdjacentEdge { data: ajacent_edge },
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetPrevAdjacentEdge { data: ajacent_edge },
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
|
@ -81,6 +81,7 @@ lazy_static! {
|
||||
Box::new(crate::std::sketch::Arc),
|
||||
Box::new(crate::std::sketch::TangentialArc),
|
||||
Box::new(crate::std::sketch::TangentialArcTo),
|
||||
Box::new(crate::std::sketch::TangentialArcToRelative),
|
||||
Box::new(crate::std::sketch::BezierCurve),
|
||||
Box::new(crate::std::sketch::Hole),
|
||||
Box::new(crate::std::patterns::PatternLinear2D),
|
||||
|
@ -14,7 +14,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{
|
||||
BasePath, ExtrudeGroup, Face, GeoMeta, KclValue, Path, Plane, PlaneType, Point2d, Point3d, SketchGroup,
|
||||
SketchGroupSet, SketchSurface, SourceRange, TagEngineInfo, TagIdentifier, UserVal,
|
||||
SketchGroupSet, SketchSurface, TagEngineInfo, TagIdentifier, UserVal,
|
||||
},
|
||||
std::{
|
||||
utils::{
|
||||
@ -1634,8 +1634,6 @@ pub enum TangentialArcData {
|
||||
/// Offset of the arc, in degrees.
|
||||
offset: f64,
|
||||
},
|
||||
/// A point where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
|
||||
Point([f64; 2]),
|
||||
}
|
||||
|
||||
/// Draw a tangential arc.
|
||||
@ -1728,13 +1726,6 @@ async fn inner_tangential_arc(
|
||||
.await?;
|
||||
(center, to.into(), ccw)
|
||||
}
|
||||
TangentialArcData::Point(to) => {
|
||||
args.batch_modeling_cmd(id, tan_arc_to(&sketch_group, &to)).await?;
|
||||
// TODO: Figure out these calculations.
|
||||
let ccw = false;
|
||||
let center = Point2d { x: 0.0, y: 0.0 };
|
||||
(center, to, ccw)
|
||||
}
|
||||
};
|
||||
|
||||
let current_path = Path::TangentialArc {
|
||||
@ -1775,35 +1766,24 @@ fn tan_arc_to(sketch_group: &SketchGroup, to: &[f64; 2]) -> ModelingCmd {
|
||||
}
|
||||
}
|
||||
|
||||
fn too_few_args(source_range: SourceRange) -> KclError {
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![source_range],
|
||||
message: "too few arguments".to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_arg<I: Iterator>(it: &mut I, src: SourceRange) -> Result<I::Item, KclError> {
|
||||
it.next().ok_or_else(|| too_few_args(src))
|
||||
}
|
||||
|
||||
/// Draw a tangential arc to a specific point.
|
||||
pub async fn tangential_arc_to(args: Args) -> Result<KclValue, KclError> {
|
||||
let src = args.source_range;
|
||||
|
||||
// Get arguments to function call
|
||||
let mut it = args.args.iter();
|
||||
let to: [f64; 2] = get_arg(&mut it, src)?.get_json()?;
|
||||
let sketch_group: SketchGroup = get_arg(&mut it, src)?.get_json()?;
|
||||
let tag = if let Ok(memory_item) = get_arg(&mut it, src) {
|
||||
memory_item.get_json_opt()?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (to, sketch_group, tag): ([f64; 2], SketchGroup, Option<TagDeclarator>) =
|
||||
super::args::FromArgs::from_args(&args, 0)?;
|
||||
|
||||
let new_sketch_group = inner_tangential_arc_to(to, sketch_group, tag, args).await?;
|
||||
Ok(KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw a tangential arc to point some distance away..
|
||||
pub async fn tangential_arc_to_relative(args: Args) -> Result<KclValue, KclError> {
|
||||
let (delta, sketch_group, tag): ([f64; 2], SketchGroup, Option<TagDeclarator>) =
|
||||
super::args::FromArgs::from_args(&args, 0)?;
|
||||
|
||||
let new_sketch_group = inner_tangential_arc_to_relative(delta, sketch_group, tag, args).await?;
|
||||
Ok(KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group))
|
||||
}
|
||||
|
||||
/// Starting at the current sketch's origin, draw a curved line segment along
|
||||
/// some part of an imaginary circle until it reaches the desired (x, y)
|
||||
/// coordinates.
|
||||
@ -1873,6 +1853,90 @@ async fn inner_tangential_arc_to(
|
||||
Ok(new_sketch_group)
|
||||
}
|
||||
|
||||
/// Starting at the current sketch's origin, draw a curved line segment along
|
||||
/// some part of an imaginary circle until it reaches a point the given (x, y)
|
||||
/// distance away.
|
||||
///
|
||||
/// ```no_run
|
||||
/// const exampleSketch = startSketchOn('XZ')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> angledLine({
|
||||
/// angle: 45,
|
||||
/// length: 10,
|
||||
/// }, %)
|
||||
/// |> tangentialArcToRelative([0, -10], %)
|
||||
/// |> line([-10, 0], %)
|
||||
/// |> close(%)
|
||||
///
|
||||
/// const example = extrude(10, exampleSketch)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "tangentialArcToRelative",
|
||||
}]
|
||||
async fn inner_tangential_arc_to_relative(
|
||||
delta: [f64; 2],
|
||||
sketch_group: SketchGroup,
|
||||
tag: Option<TagDeclarator>,
|
||||
args: Args,
|
||||
) -> Result<SketchGroup, KclError> {
|
||||
let from: Point2d = sketch_group.current_pen_position()?;
|
||||
let tangent_info = sketch_group.get_tangential_info_from_paths();
|
||||
let tan_previous_point = if tangent_info.is_center {
|
||||
get_tangent_point_from_previous_arc(tangent_info.center_or_tangent_point, tangent_info.ccw, from.into())
|
||||
} else {
|
||||
tangent_info.center_or_tangent_point
|
||||
};
|
||||
let [dx, dy] = delta;
|
||||
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
|
||||
arc_start_point: [from.x, from.y],
|
||||
arc_end_point: [from.x + dx, from.y + dy],
|
||||
tan_previous_point,
|
||||
obtuse: true,
|
||||
});
|
||||
|
||||
if result.center[0].is_infinite() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: vec![args.source_range],
|
||||
message:
|
||||
"could not sketch tangential arc, because its center would be infinitely far away in the X direction"
|
||||
.to_owned(),
|
||||
}));
|
||||
} else if result.center[1].is_infinite() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: vec![args.source_range],
|
||||
message:
|
||||
"could not sketch tangential arc, because its center would be infinitely far away in the Y direction"
|
||||
.to_owned(),
|
||||
}));
|
||||
}
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
args.batch_modeling_cmd(id, tan_arc_to(&sketch_group, &delta)).await?;
|
||||
|
||||
let current_path = Path::TangentialArcTo {
|
||||
base: BasePath {
|
||||
from: from.into(),
|
||||
to: delta,
|
||||
tag: tag.clone(),
|
||||
geo_meta: GeoMeta {
|
||||
id,
|
||||
metadata: args.source_range.into(),
|
||||
},
|
||||
},
|
||||
center: dbg!(result.center),
|
||||
ccw: result.ccw > 0,
|
||||
};
|
||||
|
||||
let mut new_sketch_group = sketch_group.clone();
|
||||
if let Some(tag) = &tag {
|
||||
new_sketch_group.add_tag(tag, ¤t_path);
|
||||
}
|
||||
|
||||
new_sketch_group.value.push(current_path);
|
||||
|
||||
Ok(new_sketch_group)
|
||||
}
|
||||
|
||||
/// Data to draw a bezier curve.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
|
After Width: | Height: | Size: 58 KiB |
@ -56,10 +56,10 @@ const bracketBody = bs
|
||||
|> fillet({
|
||||
radius: radius,
|
||||
tags: [
|
||||
getNextAdjacentEdge(bs.sketchGroup.tags.edge7),
|
||||
getNextAdjacentEdge(bs.sketchGroup.tags.edge2),
|
||||
getNextAdjacentEdge(bs.sketchGroup.tags.edge3),
|
||||
getNextAdjacentEdge(bs.sketchGroup.tags.edge6)
|
||||
getPreviousAdjacentEdge(bs.sketchGroup.tags.edge7),
|
||||
getPreviousAdjacentEdge(bs.sketchGroup.tags.edge2),
|
||||
getPreviousAdjacentEdge(bs.sketchGroup.tags.edge3),
|
||||
getPreviousAdjacentEdge(bs.sketchGroup.tags.edge6)
|
||||
]
|
||||
}, %)
|
||||
|
||||
|
@ -56,10 +56,10 @@ const bracketBody = bs
|
||||
|> fillet({
|
||||
radius: radius,
|
||||
tags: [
|
||||
getNextAdjacentEdge(bs.tags.edge7),
|
||||
getNextAdjacentEdge(bs.tags.edge2),
|
||||
getNextAdjacentEdge(bs.tags.edge3),
|
||||
getNextAdjacentEdge(bs.tags.edge6)
|
||||
getPreviousAdjacentEdge(bs.tags.edge7),
|
||||
getPreviousAdjacentEdge(bs.tags.edge2),
|
||||
getPreviousAdjacentEdge(bs.tags.edge3),
|
||||
getPreviousAdjacentEdge(bs.tags.edge6)
|
||||
]
|
||||
}, %)
|
||||
|
||||
@ -89,7 +89,7 @@ const tabsR = startSketchOn(tabPlane)
|
||||
radius: holeDiam / 2,
|
||||
tags: [
|
||||
getNextAdjacentEdge(edge12),
|
||||
getNextAdjacentEdge(edge13)
|
||||
getNextAdjacentEdge(edge11)
|
||||
]
|
||||
}, %)
|
||||
|> patternLinear3d({
|
||||
|
@ -9,40 +9,40 @@ let corner_radius = 5.0
|
||||
// because your wrist isn't a perfect cylindrical surface
|
||||
let brace_base = startSketchAt([corner_radius, 0])
|
||||
|> line([width - corner_radius, 0.0], %)
|
||||
|> tangentialArc([corner_radius, corner_radius], %)
|
||||
|> tangentialArcToRelative([corner_radius, corner_radius], %)
|
||||
|> yLine(25.0 - corner_radius, %)
|
||||
|> tangentialArc([-corner_radius, corner_radius], %)
|
||||
|> tangentialArcToRelative([-corner_radius, corner_radius], %)
|
||||
|> xLine(-(d_wrist_circumference[0] - (corner_radius * 2)), %)
|
||||
|> tangentialArc([-corner_radius, corner_radius], %)
|
||||
|> tangentialArcToRelative([-corner_radius, corner_radius], %)
|
||||
|> yLine(length - 25.0 - 23.0 - (corner_radius * 2), %)
|
||||
|> tangentialArc([corner_radius, corner_radius], %)
|
||||
|> tangentialArcToRelative([corner_radius, corner_radius], %)
|
||||
|> xLine(15.0 - (corner_radius * 2), %)
|
||||
|> tangentialArc([corner_radius, corner_radius], %)
|
||||
|> tangentialArcToRelative([corner_radius, corner_radius], %)
|
||||
|> yLine(23.0 - corner_radius, %)
|
||||
|> tangentialArc([-corner_radius, corner_radius], %)
|
||||
|> tangentialArcToRelative([-corner_radius, corner_radius], %)
|
||||
|> xLine(-(hand_thickness + 15.0 + 15.0 - (corner_radius * 2)), %)
|
||||
|> tangentialArc([-corner_radius, -corner_radius], %)
|
||||
|> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|
||||
|> yLine(-(23.0 - corner_radius), %)
|
||||
|> tangentialArc([corner_radius, -corner_radius], %)
|
||||
|> tangentialArcToRelative([corner_radius, -corner_radius], %)
|
||||
|> xLine(15.0 - (corner_radius * 2), %)
|
||||
|> tangentialArc([corner_radius, -corner_radius], %)
|
||||
|> tangentialArcToRelative([corner_radius, -corner_radius], %)
|
||||
|> yLine(-(length - 25.0 - 23.0 - (corner_radius * 2)), %)
|
||||
|> tangentialArc([-corner_radius, -corner_radius], %)
|
||||
|> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|
||||
|> xLine(-(d_wrist_circumference[1] + d_wrist_circumference[2] + d_wrist_circumference[3] - hand_thickness - corner_radius), %)
|
||||
|> tangentialArc([-corner_radius, -corner_radius], %)
|
||||
|> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|
||||
|> yLine(-(25.0 - corner_radius), %)
|
||||
|> tangentialArc([corner_radius, -corner_radius], %)
|
||||
|> tangentialArcToRelative([corner_radius, -corner_radius], %)
|
||||
|> close(%)
|
||||
|
||||
let inner = startSketchAt([0, 0])
|
||||
|> xLine(1.0, %)
|
||||
|> tangentialArc([corner_radius, corner_radius], %)
|
||||
|> tangentialArcToRelative([corner_radius, corner_radius], %)
|
||||
|> yLine(25.0 - (corner_radius * 2), %)
|
||||
|> tangentialArc([-corner_radius, corner_radius], %)
|
||||
|> tangentialArcToRelative([-corner_radius, corner_radius], %)
|
||||
|> xLine(-1.0, %)
|
||||
|> tangentialArc([-corner_radius, -corner_radius], %)
|
||||
|> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|
||||
|> yLine(-(25.0 - (corner_radius * 2)), %)
|
||||
|> tangentialArc([corner_radius, -corner_radius], %)
|
||||
|> tangentialArcToRelative([corner_radius, -corner_radius], %)
|
||||
|> close(%)
|
||||
|
||||
let final = brace_base
|
||||
|
@ -186,7 +186,7 @@ async fn kcl_test_negative_args() {
|
||||
async fn kcl_test_basic_tangential_arc_with_point() {
|
||||
let code = r#"const boxSketch = startSketchAt([0, 0])
|
||||
|> line([0, 10], %)
|
||||
|> tangentialArc([-5, 5], %)
|
||||
|> tangentialArcToRelative([-5, 5], %)
|
||||
|> line([5, -15], %)
|
||||
|> extrude(10, %)
|
||||
"#;
|
||||
@ -715,7 +715,7 @@ async fn kcl_test_error_sketch_on_arc_face() {
|
||||
let code = r#"fn cube = (pos, scale) => {
|
||||
const sg = startSketchOn('XY')
|
||||
|> startProfileAt(pos, %)
|
||||
|> tangentialArc([0, scale], %, $here)
|
||||
|> tangentialArcToRelative([0, scale], %, $here)
|
||||
|> line([scale, 0], %)
|
||||
|> line([0, -scale], %)
|
||||
|
||||
@ -739,7 +739,7 @@ const part002 = startSketchOn(part001, part001.sketchGroup.tags.here)
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([280, 333])], message: "Tag `here` is a non-planar surface" }"#
|
||||
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([94, 139]), SourceRange([222, 238])], message: "could not sketch tangential arc, because its center would be infinitely far away in the X direction" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1067,10 +1067,11 @@ const sketch001 = startSketchOn(box, revolveAxis)
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
r#"engine: KclErrorDetails { source_ranges: [SourceRange([346, 390])], message: "Modeling command failed: [ApiError { error_code: InternalEngine, message: \"Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis\" }]" }"#
|
||||
);
|
||||
//this fails right now, but slightly differently, lets just say its enough for it to fail - mike
|
||||
//assert_eq!(
|
||||
// result.err().unwrap().to_string(),
|
||||
// r#"engine: KclErrorDetails { source_ranges: [SourceRange([346, 390])], message: "Modeling command failed: [ApiError { error_code: InternalEngine, message: \"Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis\" }]" }"#
|
||||
//);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1286,7 +1287,7 @@ capScrew([0, 0.5, 0], 50, 37.5, 50, 25)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_bracket_with_fillets_ensure_fail_on_flush_source_ranges() {
|
||||
async fn kcl_test_bracket_with_fillets() {
|
||||
let code = r#"// Shelf Bracket
|
||||
// This is a shelf bracket made out of 6061-T6 aluminum sheet metal. The required thickness is calculated based on a point load of 300 lbs applied to the end of the shelf. There are two brackets holding up the shelf, so the moment experienced is divided by 2. The shelf is 1 foot long from the wall.
|
||||
|
||||
@ -1327,11 +1328,7 @@ const bracket = startSketchOn('XY')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
r#"engine: KclErrorDetails { source_ranges: [SourceRange([1329, 1430])], message: "Modeling command failed: [ApiError { error_code: BadRequest, message: \"Fillet failed\" }]" }"#
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1535,13 +1532,13 @@ const bracket = startSketchOn('XY')
|
||||
|> fillet({
|
||||
radius: filletR,
|
||||
tags: [
|
||||
getPreviousAdjacentEdge(innerEdge)
|
||||
getNextAdjacentEdge(innerEdge)
|
||||
]
|
||||
}, %)
|
||||
|> fillet({
|
||||
radius: filletR + thickness,
|
||||
tags: [
|
||||
getPreviousAdjacentEdge(outerEdge)
|
||||
getNextAdjacentEdge(outerEdge)
|
||||
]
|
||||
}, %)
|
||||
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 58 KiB |
@ -49,7 +49,13 @@ export function getDefineKeys(names: string[]) {
|
||||
|
||||
export function getBuildDefine(env: ConfigEnv<'build'>) {
|
||||
const { command, forgeConfig } = env
|
||||
const names = forgeConfig.renderer
|
||||
const renderer = (forgeConfig && forgeConfig.renderer) ?? [
|
||||
{
|
||||
name: 'main_window',
|
||||
config: 'vite.renderer.config.ts',
|
||||
},
|
||||
]
|
||||
const names = renderer
|
||||
.filter(({ name }) => name != null)
|
||||
.map(({ name }) => name!)
|
||||
const defineKeys = getDefineKeys(names)
|
||||
|
@ -67,7 +67,7 @@ export default defineConfig((env) => {
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: forgeConfigSelf.entry!,
|
||||
entry: forgeConfigSelf?.entry ?? 'src/main.ts',
|
||||
fileName: () => '[name].js',
|
||||
formats: ['cjs'],
|
||||
},
|
||||
|
@ -20,7 +20,7 @@ export default defineConfig((env) => {
|
||||
base: './',
|
||||
build: {
|
||||
lib: {
|
||||
entry: forgeConfigSelf.entry!,
|
||||
entry: forgeConfigSelf?.entry ?? 'src/preload.ts',
|
||||
fileName: () => '[name].js',
|
||||
formats: ['cjs'],
|
||||
},
|
||||
|
@ -9,7 +9,7 @@ import { lezer } from '@lezer/generator/rollup'
|
||||
export default defineConfig((env) => {
|
||||
const forgeEnv = env as ConfigEnv<'renderer'>
|
||||
const { root, mode, forgeConfigSelf } = forgeEnv
|
||||
const name = forgeConfigSelf.name ?? ''
|
||||
const name = forgeConfigSelf?.name ?? 'main_window'
|
||||
|
||||
return {
|
||||
root,
|
||||
|