Compare commits

..

1 Commits

Author SHA1 Message Date
dac3c0c224 Delete test that is covering a basic assumption 2025-05-13 12:46:28 -04:00
1073 changed files with 536890 additions and 454317 deletions

View File

@ -13,7 +13,7 @@ env:
IS_NIGHTLY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} IS_NIGHTLY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
@ -123,6 +123,18 @@ jobs:
- id: export_notes - id: export_notes
run: echo "notes=`cat release-notes.md`" >> "$GITHUB_OUTPUT" run: echo "notes=`cat release-notes.md`" >> "$GITHUB_OUTPUT"
- name: Prepare electron-builder.yml file for updater test
if: ${{ env.IS_RELEASE == 'true' }}
run: |
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test"' electron-builder.yml
- uses: actions/upload-artifact@v4
if: ${{ env.IS_RELEASE == 'true' }}
with:
name: prepared-files-updater-test
path: |
electron-builder.yml
build-apps: build-apps:
needs: [prepare-files] needs: [prepare-files]
@ -247,6 +259,49 @@ jobs:
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back # TODO: add the 'Build for Mac TestFlight (nightly)' stage back
# The steps below are for updater-test builds, only on release
- uses: actions/download-artifact@v4
if: ${{ env.IS_RELEASE == 'true' }}
name: prepared-files-updater-test
- name: Copy updated electron-builder.yml file for updater test
if: ${{ env.IS_RELEASE == 'true' }}
run: |
ls -R prepared-files-updater-test
cp prepared-files-updater-test/electron-builder.yml electron-builder.yml
- name: Build the app (updater-test)
if: ${{ env.IS_RELEASE == 'true' }}
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 }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: npm run tronb:package:prod
- uses: actions/upload-artifact@v4
if: ${{ env.IS_RELEASE == 'true' }}
with:
name: updater-test-arm64-${{ matrix.platform }}
path: |
out/*-arm64-win.exe
out/*-arm64-mac.dmg
out/*-arm64-linux.AppImage
- uses: actions/upload-artifact@v4
if: ${{ env.IS_RELEASE == 'true' }}
with:
name: updater-test-x64-${{ matrix.platform }}
path: |
out/*-x64-win.exe
out/*-x64-mac.dmg
out/*-x86_64-linux.AppImage
upload-apps-release: upload-apps-release:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04

View File

@ -1,56 +0,0 @@
name: Build WASM
on:
workflow_call:
permissions:
contents: read
jobs:
npm-build-wasm:
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Use correct Rust toolchain
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # configured below
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
with:
tool: wasm-pack
- name: Use Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: Build Wasm
shell: bash
run: npm run build:wasm
- uses: actions/upload-artifact@v4
with:
name: prepared-wasm
path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
- uses: actions/upload-artifact@v4
with:
name: prepared-ts-rs-bindings
path: |
rust/kcl-lib/bindings/*

View File

@ -88,7 +88,6 @@ jobs:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
RUST_BACKTRACE: full RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Commit differences - name: Commit differences
if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure' if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure'
shell: bash shell: bash
@ -120,7 +119,6 @@ jobs:
# Configure nextest when it's run by insta (via just). # Configure nextest when it's run by insta (via just).
NEXTEST_PROFILE: ci NEXTEST_PROFILE: ci
RUST_BACKTRACE: full RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Build and archive tests - name: Build and archive tests
run: | run: |
cd rust cd rust
@ -157,7 +155,7 @@ jobs:
shell: bash shell: bash
run: | run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ [ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install Rust - name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
cache: false # Configured below. cache: false # Configured below.
@ -184,7 +182,6 @@ jobs:
env: env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
RUST_MIN_STACK: 10485760000
- name: Upload results - name: Upload results
if: always() if: always()
run: .github/ci-cd-scripts/upload-results.sh run: .github/ci-cd-scripts/upload-results.sh
@ -193,55 +190,6 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }} CI_PR_NUMBER: ${{ github.event.pull_request.number }}
run-internal-kcl-samples:
name: cargo test (internal-kcl-samples)
runs-on:
- runs-on=${{ github.run_id }}
- runner=32cpu-linux-x64
- extras=s3-cache
steps:
- uses: runs-on/action@v1
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- name: Use correct Rust toolchain
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
- name: Start Vector
run: .github/ci-cd-scripts/start-vector-ubuntu.sh
env:
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
OS_NAME: ${{ env.OS_NAME }}
- uses: taiki-e/install-action@nextest
- name: Download internal KCL samples
run: git clone --depth=1 https://x-access-token:${{ secrets.GH_PAT_KCL_SAMPLES_INTERNAL }}@github.com/KittyCAD/kcl-samples-internal public/kcl-samples/internal
- name: Run tests
shell: bash
run: |-
cd rust/kcl-lib
cargo nextest run \
--retries=10 --no-fail-fast --features artifact-graph --profile=ci \
internal \
2>&1 | tee /tmp/github-actions.log
env:
TWENTY_TWENTY: overwrite
INSTA_UPDATE: always
EXPECTORATE: overwrite
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev
MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}}
RUST_MIN_STACK: 10485760000
run-wasm-tests: run-wasm-tests:
name: Run wasm tests name: Run wasm tests
strategy: strategy:

View File

@ -40,7 +40,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm install run: npm install
- name: Download Wasm cache - name: Download Wasm Cache
id: download-wasm id: download-wasm
if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }} if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7 uses: dawidd6/action-download-artifact@v7
@ -52,7 +52,7 @@ jobs:
branch: main branch: main
path: rust/kcl-wasm-lib/pkg path: rust/kcl-wasm-lib/pkg
- name: Build Wasm condition - name: Build WASM condition
id: wasm id: wasm
run: | run: |
set -euox pipefail set -euox pipefail
@ -70,7 +70,7 @@ jobs:
run: | run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ [ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install Rust - name: Install rust
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
@ -81,7 +81,7 @@ jobs:
with: with:
tool: wasm-pack tool: wasm-pack
- name: Use Rust cache - name: Rust Cache
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
@ -117,7 +117,7 @@ jobs:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
name: prepared-wasm name: prepared-wasm
- name: Copy prepared Wasm - name: Copy prepared wasm
run: | run: |
ls -R prepared-wasm ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -133,17 +133,20 @@ jobs:
id: deps-install id: deps-install
run: npm install run: npm install
- name: Cache browsers - name: Cache Playwright Browsers
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
~/.cache/ms-playwright/ ~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install browsers - name: Install Playwright Browsers
run: npm run playwright install --with-deps run: npm run playwright install --with-deps
- name: Capture snapshots - name: build web
run: npm run tronb:vite:dev
- name: Run ubuntu/chrome snapshots
uses: nick-fields/retry@v3.0.2 uses: nick-fields/retry@v3.0.2
with: with:
shell: bash shell: bash
@ -167,6 +170,33 @@ jobs:
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
- name: Check for changes
if: ${{ github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
if git status | grep -q "Changes to be committed"
then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
# TODO: find a more reliable way to detect visual changes
if: ${{ false && steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit -m "A snapshot a day keeps the bugs away! 📷🐛" || true
git push
git push origin ${{ github.head_ref }}
electron: electron:
needs: [prepare-wasm] needs: [prepare-wasm]
timeout-minutes: 60 timeout-minutes: 60
@ -204,16 +234,10 @@ jobs:
shardTotal: 8 shardTotal: 8
- os: namespace-profile-macos-8-cores - os: namespace-profile-macos-8-cores
shardIndex: 1 shardIndex: 1
shardTotal: 2 shardTotal: 1
- os: namespace-profile-macos-8-cores
shardIndex: 2
shardTotal: 2
- os: windows-latest-8-cores - os: windows-latest-8-cores
shardIndex: 1 shardIndex: 1
shardTotal: 2 shardTotal: 1
- os: windows-latest-8-cores
shardIndex: 2
shardTotal: 2
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@ -21,11 +21,14 @@ on:
- '**.rs' - '**.rs'
- .github/workflows/kcl-language-server.yml - .github/workflows/kcl-language-server.yml
workflow_dispatch: workflow_dispatch:
permissions: permissions:
contents: read contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true
env: env:
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10 CARGO_NET_RETRY: 10
@ -35,9 +38,10 @@ env:
MACOSX_DEPLOYMENT_TARGET: 10.15 MACOSX_DEPLOYMENT_TARGET: 10.15
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
jobs: jobs:
test: test:
name: kcl-language-server (vscode tests) name: vscode tests
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -73,7 +77,8 @@ jobs:
include: include:
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
code-target: win32-x64 code-target:
win32-x64
#- os: windows-latest #- os: windows-latest
#target: i686-pc-windows-msvc #target: i686-pc-windows-msvc
#code-target: #code-target:
@ -83,7 +88,8 @@ jobs:
#code-target: win32-arm64 #code-target: win32-arm64
- os: ubuntu-latest - os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
code-target: linux-x64 code-target:
linux-x64
#- os: ubuntu-latest #- os: ubuntu-latest
#target: aarch64-unknown-linux-musl #target: aarch64-unknown-linux-musl
#code-target: linux-arm64 #code-target: linux-arm64
@ -99,33 +105,41 @@ jobs:
- os: macos-latest - os: macos-latest
target: aarch64-apple-darwin target: aarch64-apple-darwin
code-target: darwin-arm64 code-target: darwin-arm64
name: kcl-language-server build-release (${{ matrix.target }})
name: build-release (${{ matrix.target }})
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
container: ${{ matrix.container }} container: ${{ matrix.container }}
env: env:
RA_TARGET: ${{ matrix.target }} RA_TARGET: ${{ matrix.target }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: ${{ env.FETCH_DEPTH }} fetch-depth: ${{ env.FETCH_DEPTH }}
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
shell: bash shell: bash
run: | run: |
rm rust/rust-toolchain.toml rm rust/rust-toolchain.toml
- name: Install rust - name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
cache: rust cache: rust
components: rust-src components: rust-src
target: ${{ matrix.target }} target: ${{ matrix.target }}
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
- name: Update apt repositories - name: Update apt repositories
if: matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'arm-unknown-linux-gnueabihf' || matrix.os == 'ubuntu-latest' if: matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'arm-unknown-linux-gnueabihf' || matrix.os == 'ubuntu-latest'
run: sudo apt-get update run: sudo apt-get update
- if: ${{ matrix.os == 'ubuntu-latest' }} - if: ${{ matrix.os == 'ubuntu-latest' }}
name: Install deps name: Install deps
shell: bash shell: bash
@ -150,53 +164,64 @@ jobs:
zlib1g-dev zlib1g-dev
cargo install cross cargo install cross
- name: Install AArch64 target toolchain - name: Install AArch64 target toolchain
if: matrix.target == 'aarch64-unknown-linux-gnu' if: matrix.target == 'aarch64-unknown-linux-gnu'
run: sudo apt-get install gcc-aarch64-linux-gnu run: sudo apt-get install gcc-aarch64-linux-gnu
- name: Install ARM target toolchain - name: Install ARM target toolchain
if: matrix.target == 'arm-unknown-linux-gnueabihf' if: matrix.target == 'arm-unknown-linux-gnueabihf'
run: sudo apt-get install gcc-arm-linux-gnueabihf run: sudo apt-get install gcc-arm-linux-gnueabihf
- name: build - name: build
run: | run: |
cd rust cd rust
cargo kcl-language-server-release build --client-patch-version ${{ github.run_number }} cargo kcl-language-server-release build --client-patch-version ${{ github.run_number }}
- name: Install dependencies - name: Install dependencies
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
# npm will symlink which will cause issues w tarballing later # npm will symlink which will cause issues w tarballing later
yarn install yarn install
- name: Package Extension (release) - name: Package Extension (release)
if: startsWith(github.event.ref, 'refs/tags/') if: startsWith(github.event.ref, 'refs/tags/')
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
npx vsce package --yarn -o "../build/kcl-language-server-${{ matrix.code-target }}.vsix" --target ${{ matrix.code-target }} npx vsce package --yarn -o "../build/kcl-language-server-${{ matrix.code-target }}.vsix" --target ${{ matrix.code-target }}
- name: Package Extension (nightly) - name: Package Extension (nightly)
if: startsWith(github.event.ref, 'refs/tags/') == false if: startsWith(github.event.ref, 'refs/tags/') == false
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
npx vsce package --yarn -o "../build/kcl-language-server-${{ matrix.code-target }}.vsix" --target ${{ matrix.code-target }} --pre-release npx vsce package --yarn -o "../build/kcl-language-server-${{ matrix.code-target }}.vsix" --target ${{ matrix.code-target }} --pre-release
- name: remove server - name: remove server
if: matrix.target == 'x86_64-unknown-linux-gnu' if: matrix.target == 'x86_64-unknown-linux-gnu'
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
rm -rf server rm -rf server
- name: Package Extension (no server, release) - name: Package Extension (no server, release)
if: matrix.target == 'x86_64-unknown-linux-gnu' && startsWith(github.event.ref, 'refs/tags/') if: matrix.target == 'x86_64-unknown-linux-gnu' && startsWith(github.event.ref, 'refs/tags/')
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
npx vsce package --yarn -o ../build/kcl-language-server-no-server.vsix npx vsce package --yarn -o ../build/kcl-language-server-no-server.vsix
- name: Package Extension (no server, nightly) - name: Package Extension (no server, nightly)
if: matrix.target == 'x86_64-unknown-linux-gnu' && startsWith(github.event.ref, 'refs/tags/') == false if: matrix.target == 'x86_64-unknown-linux-gnu' && startsWith(github.event.ref, 'refs/tags/') == false
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
npx vsce package --yarn -o ../build/kcl-language-server-no-server.vsix --pre-release npx vsce package --yarn -o ../build/kcl-language-server-no-server.vsix --pre-release
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-${{ matrix.target }} name: release-${{ matrix.target }}
path: ./rust/build path: ./rust/build
build-release-x86_64-unknown-linux-musl: build-release-x86_64-unknown-linux-musl:
name: kcl-language-server build-release (x86_64-unknown-linux-musl) name: build-release (x86_64-unknown-linux-musl)
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
RA_TARGET: x86_64-unknown-linux-musl RA_TARGET: x86_64-unknown-linux-musl
@ -206,6 +231,7 @@ jobs:
image: alpine:latest image: alpine:latest
volumes: volumes:
- /usr/local/cargo/registry:/usr/local/cargo/registry - /usr/local/cargo/registry:/usr/local/cargo/registry
steps: steps:
- name: Install dependencies - name: Install dependencies
run: | run: |
@ -219,46 +245,55 @@ jobs:
nodejs \ nodejs \
npm \ npm \
yarn yarn
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: ${{ env.FETCH_DEPTH }} fetch-depth: ${{ env.FETCH_DEPTH }}
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
shell: bash shell: bash
run: | run: |
rm rust/rust-toolchain.toml rm rust/rust-toolchain.toml
- name: Install rust - name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
cache: rust cache: rust
components: rust-src components: rust-src
target: ${{ matrix.target }} target: ${{ matrix.target }}
- name: build - name: build
run: | run: |
cd rust cd rust
cargo kcl-language-server-release build --client-patch-version ${{ github.run_number }} cargo kcl-language-server-release build --client-patch-version ${{ github.run_number }}
- name: Install dependencies - name: Install dependencies
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
# npm will symlink which will cause issues w tarballing later # npm will symlink which will cause issues w tarballing later
yarn install yarn install
- name: Package Extension (release) - name: Package Extension (release)
if: startsWith(github.event.ref, 'refs/tags/') if: startsWith(github.event.ref, 'refs/tags/')
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
npx vsce package --yarn -o "../build/kcl-language-server-alpine-x64.vsix" --target alpine-x64 npx vsce package --yarn -o "../build/kcl-language-server-alpine-x64.vsix" --target alpine-x64
- name: Package Extension (release) - name: Package Extension (release)
if: startsWith(github.event.ref, 'refs/tags/') == false if: startsWith(github.event.ref, 'refs/tags/') == false
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
npx vsce package --yarn -o "../build/kcl-language-server-alpine-x64.vsix" --target alpine-x64 --pre-release npx vsce package --yarn -o "../build/kcl-language-server-alpine-x64.vsix" --target alpine-x64 --pre-release
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-x86_64-unknown-linux-musl name: release-x86_64-unknown-linux-musl
path: ./rust/build path: ./rust/build
publish: publish:
name: kcl-language-server (publish) name: publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: ["build-release", "build-release-x86_64-unknown-linux-musl"] needs: ["build-release", "build-release-x86_64-unknown-linux-musl"]
if: startsWith(github.event.ref, 'refs/tags') if: startsWith(github.event.ref, 'refs/tags')
@ -266,17 +301,22 @@ jobs:
contents: write contents: write
steps: steps:
- run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- run: 'echo "TAG: $TAG"' - run: 'echo "TAG: $TAG"'
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: ${{ env.FETCH_DEPTH }} fetch-depth: ${{ env.FETCH_DEPTH }}
- name: Install Nodejs - name: Install Nodejs
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
- run: echo "HEAD_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV - run: echo "HEAD_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV
- run: 'echo "HEAD_SHA: $HEAD_SHA"' - run: 'echo "HEAD_SHA: $HEAD_SHA"'
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
name: release-aarch64-apple-darwin name: release-aarch64-apple-darwin
@ -304,7 +344,8 @@ jobs:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
name: release-x86_64-pc-windows-msvc name: release-x86_64-pc-windows-msvc
path: rust/build path:
rust/build
#- uses: actions/download-artifact@v4 #- uses: actions/download-artifact@v4
#with: #with:
#name: release-i686-pc-windows-msvc #name: release-i686-pc-windows-msvc
@ -315,18 +356,21 @@ jobs:
#name: release-aarch64-pc-windows-msvc #name: release-aarch64-pc-windows-msvc
#path: rust/build #path: rust/build
- run: ls -al ./rust/build - run: ls -al ./rust/build
- name: Publish Release - name: Publish Release
uses: ./.github/actions/github-release uses: ./.github/actions/github-release
with: with:
files: "rust/build/*" files: "rust/build/*"
name: ${{ env.TAG }} name: ${{ env.TAG }}
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: move files to dir for upload - name: move files to dir for upload
shell: bash shell: bash
run: | run: |
cd rust cd rust
mkdir -p releases/language-server/${{ env.TAG }} mkdir -p releases/language-server/${{ env.TAG }}
cp -r build/* releases/language-server/${{ env.TAG }} cp -r build/* releases/language-server/${{ env.TAG }}
- name: "Authenticate to Google Cloud" - name: "Authenticate to Google Cloud"
uses: "google-github-actions/auth@v2.1.8" uses: "google-github-actions/auth@v2.1.8"
with: with:
@ -341,12 +385,15 @@ jobs:
with: with:
path: rust/releases path: rust/releases
destination: dl.kittycad.io destination: dl.kittycad.io
- run: rm rust/build/kcl-language-server-no-server.vsix - run: rm rust/build/kcl-language-server-no-server.vsix
- name: Publish Extension (Code Marketplace, release) - name: Publish Extension (Code Marketplace, release)
# token from https://dev.azure.com/kcl-language-server/ # token from https://dev.azure.com/kcl-language-server/
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server
npx vsce publish --pat ${{ secrets.VSCE_PAT }} --packagePath ../build/kcl-language-server-*.vsix npx vsce publish --pat ${{ secrets.VSCE_PAT }} --packagePath ../build/kcl-language-server-*.vsix
- name: Publish Extension (OpenVSX, release) - name: Publish Extension (OpenVSX, release)
run: | run: |
cd rust/kcl-language-server cd rust/kcl-language-server

View File

@ -4,6 +4,7 @@
# maturin generate-ci github # maturin generate-ci github
# #
name: kcl-python-bindings name: kcl-python-bindings
on: on:
push: push:
branches: branches:
@ -26,14 +27,16 @@ on:
- '**.rs' - '**.rs'
- .github/workflows/kcl-python-bindings.yml - .github/workflows/kcl-python-bindings.yml
workflow_dispatch: workflow_dispatch:
permissions: permissions:
contents: read contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
linux-x86_64: linux-x86_64:
name: kcl-python-bindings (linux-x86_64)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -55,8 +58,8 @@ jobs:
with: with:
name: wheels-linux-x86_64 name: wheels-linux-x86_64
path: rust/kcl-python-bindings/dist path: rust/kcl-python-bindings/dist
windows: windows:
name: kcl-python-bindings (windows)
runs-on: windows-16-cores runs-on: windows-16-cores
strategy: strategy:
matrix: matrix:
@ -81,8 +84,8 @@ jobs:
with: with:
name: wheels-windows-${{ matrix.target }} name: wheels-windows-${{ matrix.target }}
path: rust/kcl-python-bindings/dist path: rust/kcl-python-bindings/dist
macos: macos:
name: kcl-python-bindings (macos)
runs-on: macos-latest runs-on: macos-latest
strategy: strategy:
matrix: matrix:
@ -107,8 +110,8 @@ jobs:
with: with:
name: wheels-macos-${{ matrix.target }} name: wheels-macos-${{ matrix.target }}
path: rust/kcl-python-bindings/dist path: rust/kcl-python-bindings/dist
test: test:
name: kcl-python-bindings (test)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -124,8 +127,8 @@ jobs:
env: env:
KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
sdist: sdist:
name: kcl-python-bindings (sdist)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -148,6 +151,7 @@ jobs:
with: with:
name: wheels-sdist name: wheels-sdist
path: rust/kcl-python-bindings/dist path: rust/kcl-python-bindings/dist
release: release:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -28,7 +28,53 @@ jobs:
- run: npm run fmt:check - run: npm run fmt:check
npm-build-wasm: npm-build-wasm:
uses: ./.github/workflows/build-wasm.yml # Build the wasm blob once on the fastest runner.
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Use correct Rust toolchain
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
with:
tool: wasm-pack
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: Build Wasm
shell: bash
run: npm run build:wasm
- uses: actions/upload-artifact@v4
with:
name: prepared-wasm
path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
- uses: actions/upload-artifact@v4
with:
name: prepared-ts-rs-bindings
path: |
rust/kcl-lib/bindings/*
npm-tsc: npm-tsc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -127,3 +173,122 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Run codespell - name: Run codespell
uses: crate-ci/typos@v1.32.0 uses: crate-ci/typos@v1.32.0
npm-unit-test-kcl-samples:
runs-on: ubuntu-latest
needs: npm-build-wasm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm install
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
with:
tool: wasm-pack
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: npm run simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
- name: Install Chromium Browser
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run playwright install chromium --with-deps
- name: Download internal KCL samples
run: git clone --depth=1 https://x-access-token:${{ secrets.GH_PAT_KCL_SAMPLES_INTERNAL }}@github.com/KittyCAD/kcl-samples-internal public/kcl-samples/internal
- name: Regenerate KCL samples manifest
run: cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
- name: Check public and internal KCL samples
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run test:unit:kcl-samples
env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
npm-unit-test:
runs-on: ubuntu-latest
needs: npm-build-wasm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm install
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
with:
tool: wasm-pack
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: npm run simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
- name: Install Chromium Browser
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run playwright install chromium --with-deps
- name: Run unit tests
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: xvfb-run -a npm run test:unit
env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: Check for changes
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
id: git-check
run: |
git add src/lang/std/artifactMapGraphs
if git status src/lang/std/artifactMapGraphs | grep -q "Changes to be committed"
then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' && steps.git-check.outputs.modified == 'true' }}
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
# TODO when webkit works on ubuntu remove the os part of the commit message
git commit -am "Look at this (photo)Graph *in the voice of Nickelback*" || true
git push
git push origin ${{ github.head_ref }}

View File

@ -1,124 +0,0 @@
name: Unit Tests
on:
pull_request:
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
actions: read
jobs:
npm-build-wasm:
uses: ./.github/workflows/build-wasm.yml
npm-test-unit:
runs-on: ubuntu-latest
needs: npm-build-wasm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm install
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
with:
tool: wasm-pack
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: npm run simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
- name: Install Chromium Browser
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run playwright install chromium --with-deps
- name: Run unit tests
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: xvfb-run -a npm run test:unit
env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: Check for changes
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
id: git-check
run: |
git add src/lang/std/artifactMapGraphs
if git status src/lang/std/artifactMapGraphs | grep -q "Changes to be committed"
then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' && steps.git-check.outputs.modified == 'true' }}
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
# TODO when webkit works on ubuntu remove the os part of the commit message
git commit -am "Look at this (photo)Graph *in the voice of Nickelback*" || true
git push
git push origin ${{ github.head_ref }}
npm-test-unit-components:
runs-on: ubuntu-latest
needs: npm-build-wasm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm install
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
with:
tool: wasm-pack
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- name: Run component tests
run: npm run test:unit:components

2
.gitignore vendored
View File

@ -58,8 +58,6 @@ trace.zip
/public/kcl-samples/.github /public/kcl-samples/.github
/public/kcl-samples/screenshots/main.kcl /public/kcl-samples/screenshots/main.kcl
/public/kcl-samples/step/main.kcl /public/kcl-samples/step/main.kcl
/public/kcl-samples/internal
/rust/kcl-lib/tests/kcl_samples/internal
/test-results/ /test-results/
/playwright-report/ /playwright-report/
/blob-report/ /blob-report/

View File

@ -130,7 +130,7 @@ git tag $VERSION
git push origin --tags git push origin --tags
``` ```
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files. This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files as well as updater-test artifacts.
The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush)). The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush)).
@ -142,10 +142,13 @@ The release builds can be found under the `out-{arch}-{platform}` zip files, at
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue. Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
A prompt should show up asking for a downgrade to the last release version. Running through that at the end of testing ##### Updater-test builds
and making sure the current release candidate has the ability to be updated to what electron-updater points to is critical,
but what is actually being downloaded and installed isn't. The other `build-apps` output in the release `build-apps` workflow (triggered by 2.) is `updater-test-{arch}-{platform}`. It's a semi-automated process: for macOS, Windows, and Linux, download the corresponding updater-test artifact file, install the app, run it, expect an updater prompt to a dummy v0.255.255, install it and check that the app comes back at that version.
If the prompt doesn't show up, start the app in command line to grab the electron-updater logs. This is likely an issue with the current build that needs addressing.
The only difference with these builds is that they point to a different update location on the release bucket, with this dummy v0.255.255 always available. This helps ensuring that the version we release will be able to update to the next one available.
If the prompt doesn't show up, start the app in command line to grab the electron-updater logs. This is likely an issue with the current build that needs addressing (or the updater-test location in the storage bucket).
``` ```
# Windows (PowerShell) # Windows (PowerShell)

View File

@ -114,6 +114,7 @@ test-unit: install ## Run the unit tests
npm run test:unit:components npm run test:unit:components
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 ) @ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
npm run test:unit npm run test:unit
npm run test:unit:kcl-samples
.PHONY: test-e2e .PHONY: test-e2e
test-e2e: test-e2e-$(TARGET) test-e2e: test-e2e-$(TARGET)

View File

@ -34,7 +34,7 @@ The 3D view in Design Studio is just a video stream from our hosted geometry eng
- WebSockets (via [KittyCAD TS client](https://github.com/KittyCAD/kittycad.ts)) - WebSockets (via [KittyCAD TS client](https://github.com/KittyCAD/kittycad.ts))
- Code Editor - Code Editor
- [CodeMirror](https://codemirror.net/) - [CodeMirror](https://codemirror.net/)
- [Custom WASM LSP Server](https://github.com/KittyCAD/modeling-app/tree/main/rust/kcl-lib/src/lsp/kcl) - Custom WASM LSP Server
- Modeling - Modeling
- [KittyCAD TypeScript client](https://github.com/KittyCAD/kittycad.ts) - [KittyCAD TypeScript client](https://github.com/KittyCAD/kittycad.ts)
@ -42,6 +42,8 @@ The 3D view in Design Studio is just a video stream from our hosted geometry eng
We recommend downloading the latest application binary from our [releases](https://github.com/KittyCAD/modeling-app/releases) page. If you don't see your platform or architecture supported there, please file an issue. We recommend downloading the latest application binary from our [releases](https://github.com/KittyCAD/modeling-app/releases) page. If you don't see your platform or architecture supported there, please file an issue.
If you'd like to try out upcoming changes sooner, you can also download those from our [nightly releases](https://zoo.dev/modeling-app/download/nightly) page.
## Developing ## Developing
Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started. Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started.

View File

@ -29,7 +29,6 @@ THRE = "THRE" # Weird bug that wrongly detects THREEjs as a typo
nwo = "nwo" # don't know what this is about tbh nwo = "nwo" # don't know what this is about tbh
"ot" = "ot" # some abbreviation, idk what "ot" = "ot" # some abbreviation, idk what
"oe" = "oe" # some abbreviation, idk what "oe" = "oe" # some abbreviation, idk what
"colinear" = "colinear" # some engine shit, kidding
[default] [default]
extend-ignore-identifiers-re = [ extend-ignore-identifiers-re = [

View File

@ -4,7 +4,7 @@ excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual layout: manual
--- ---
This is a reference for KCL. If you are learning KCL, you may prefer the [guide](https://zoo.dev/docs/kcl-book/intro.html) which explains This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains
things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std). things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std).
## Topics ## Topics

View File

@ -177,7 +177,7 @@ You can also import the whole module. This is useful if you want to use the
result of a module as a variable, like a part. result of a module as a variable, like a part.
```norun ```norun
import "cube.kcl" import "tests/inputs/cube.kcl" as cube
cube cube
|> translate(x=10) |> translate(x=10)
``` ```
@ -241,7 +241,7 @@ If you want to have multiple instances of the same object, you can use the
[`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory. [`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory.
```norun ```norun
import cube from "cube.kcl" import cube from "tests/inputs/cube.kcl"
cube cube
|> translate(x=10) |> translate(x=10)
@ -257,7 +257,7 @@ separate objects in memory, and can be manipulated independently.
Here is an example with a file from another CAD system: Here is an example with a file from another CAD system:
```kcl ```kcl
import "tests/inputs/cube.step" import "tests/inputs/cube.step" as cube
cube cube
|> translate(x=10) |> translate(x=10)

File diff suppressed because one or more lines are too long

View File

@ -1,16 +0,0 @@
---
title: "sweep::SKETCH_PLANE"
subtitle: "Constant in std::sweep"
excerpt: "Local/relative to a position centered within the plane being sketched on"
layout: manual
---
Local/relative to a position centered within the plane being sketched on
```kcl
sweep::SKETCH_PLANE: string = 'sketchPlane'
```

View File

@ -1,16 +0,0 @@
---
title: "sweep::TRAJECTORY"
subtitle: "Constant in std::sweep"
excerpt: "Local/relative to the trajectory curve"
layout: manual
---
Local/relative to the trajectory curve
```kcl
sweep::TRAJECTORY: string = 'trajectoryCurve'
```

View File

@ -12,7 +12,7 @@ reduce(
@array: [any], @array: [any],
initial: any, initial: any,
f: fn(any, accum: any): any, f: fn(any, accum: any): any,
): any ): [any]
``` ```
Take a starting value. Then, for each element of an array, calculate the next value, Take a starting value. Then, for each element of an array, calculate the next value,
@ -28,7 +28,7 @@ using the previous value and the element.
### Returns ### Returns
[`any`](/docs/kcl-std/types/std-types-any) [`[any]`](/docs/kcl-std/types/std-types-any)
### Examples ### Examples

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@ Compute the length of the given leg.
legLen( legLen(
hypotenuse: number(Length), hypotenuse: number(Length),
leg: number(Length), leg: number(Length),
): number(Length) ): number(deg)
``` ```
@ -25,7 +25,7 @@ legLen(
### Returns ### Returns
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number. [`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

File diff suppressed because one or more lines are too long

View File

@ -14,6 +14,8 @@ mirror2d(
): Sketch ): Sketch
``` ```
Only works on unclosed sketches for now.
Mirror occurs around a local sketch axis rather than a global axis. Mirror occurs around a local sketch axis rather than a global axis.
### Arguments ### Arguments

View File

@ -65,7 +65,7 @@ layout: manual
* [`line`](/docs/kcl-std/line) * [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/loft) * [`loft`](/docs/kcl-std/loft)
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d) * [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d) * [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
* [`polygon`](/docs/kcl-std/polygon) * [`polygon`](/docs/kcl-std/polygon)
* [`profileStart`](/docs/kcl-std/profileStart) * [`profileStart`](/docs/kcl-std/profileStart)
* [`profileStartX`](/docs/kcl-std/profileStartX) * [`profileStartX`](/docs/kcl-std/profileStartX)
@ -94,7 +94,7 @@ layout: manual
* [`intersect`](/docs/kcl-std/intersect) * [`intersect`](/docs/kcl-std/intersect)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d) * [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d) * [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform) * [`patternTransform`](/docs/kcl-std/patternTransform)
* [`shell`](/docs/kcl-std/functions/std-solid-shell) * [`shell`](/docs/kcl-std/functions/std-solid-shell)
* [`subtract`](/docs/kcl-std/subtract) * [`subtract`](/docs/kcl-std/subtract)
* [`union`](/docs/kcl-std/union) * [`union`](/docs/kcl-std/union)
@ -128,9 +128,6 @@ layout: manual
* [`E`](/docs/kcl-std/consts/std-math-E) * [`E`](/docs/kcl-std/consts/std-math-E)
* [`PI`](/docs/kcl-std/consts/std-math-PI) * [`PI`](/docs/kcl-std/consts/std-math-PI)
* [`TAU`](/docs/kcl-std/consts/std-math-TAU) * [`TAU`](/docs/kcl-std/consts/std-math-TAU)
* [**std::sweep**](/docs/kcl-std/modules/std-sweep)
* [`sweep::SKETCH_PLANE`](/docs/kcl-std/consts/std-sweep-SKETCH_PLANE)
* [`sweep::TRAJECTORY`](/docs/kcl-std/consts/std-sweep-TRAJECTORY)
* [**std::turns**](/docs/kcl-std/modules/std-turns) * [**std::turns**](/docs/kcl-std/modules/std-turns)
* [`turns::HALF_TURN`](/docs/kcl-std/consts/std-turns-HALF_TURN) * [`turns::HALF_TURN`](/docs/kcl-std/consts/std-turns-HALF_TURN)
* [`turns::QUARTER_TURN`](/docs/kcl-std/consts/std-turns-QUARTER_TURN) * [`turns::QUARTER_TURN`](/docs/kcl-std/consts/std-turns-QUARTER_TURN)

File diff suppressed because one or more lines are too long

View File

@ -30,7 +30,7 @@ This module contains functions for creating and manipulating sketches, and makin
* [`line`](/docs/kcl-std/line) * [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/loft) * [`loft`](/docs/kcl-std/loft)
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d) * [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d) * [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
* [`polygon`](/docs/kcl-std/polygon) * [`polygon`](/docs/kcl-std/polygon)
* [`profileStart`](/docs/kcl-std/profileStart) * [`profileStart`](/docs/kcl-std/profileStart)
* [`profileStartX`](/docs/kcl-std/profileStartX) * [`profileStartX`](/docs/kcl-std/profileStartX)

View File

@ -18,7 +18,7 @@ This module contains functions for modifying solids, e.g., by adding a fillet or
* [`intersect`](/docs/kcl-std/intersect) * [`intersect`](/docs/kcl-std/intersect)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d) * [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d) * [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform) * [`patternTransform`](/docs/kcl-std/patternTransform)
* [`shell`](/docs/kcl-std/functions/std-solid-shell) * [`shell`](/docs/kcl-std/functions/std-solid-shell)
* [`subtract`](/docs/kcl-std/subtract) * [`subtract`](/docs/kcl-std/subtract)
* [`union`](/docs/kcl-std/union) * [`union`](/docs/kcl-std/union)

View File

@ -1,17 +0,0 @@
---
title: "sweep"
subtitle: "Module in std"
excerpt: ""
layout: manual
---
## Functions and constants
* [`sweep::SKETCH_PLANE`](/docs/kcl-std/consts/std-sweep-SKETCH_PLANE)
* [`sweep::TRAJECTORY`](/docs/kcl-std/consts/std-sweep-TRAJECTORY)

View File

@ -11,7 +11,7 @@ Contains frequently used constants, functions for interacting with the KittyCAD
The standard library is organised into modules (listed below), but most things are always available in KCL programs. The standard library is organised into modules (listed below), but most things are always available in KCL programs.
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide](https://zoo.dev/docs/kcl-book/intro.html). You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide]().
## Modules ## Modules
@ -19,7 +19,6 @@ You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL gui
* [`math`](/docs/kcl-std/modules/std-math) * [`math`](/docs/kcl-std/modules/std-math)
* [`sketch`](/docs/kcl-std/modules/std-sketch) * [`sketch`](/docs/kcl-std/modules/std-sketch)
* [`solid`](/docs/kcl-std/modules/std-solid) * [`solid`](/docs/kcl-std/modules/std-solid)
* [`sweep::sweep`](/docs/kcl-std/modules/std-sweep)
* [`transform`](/docs/kcl-std/modules/std-transform) * [`transform`](/docs/kcl-std/modules/std-transform)
* [`turns::turns`](/docs/kcl-std/modules/std-turns) * [`turns::turns`](/docs/kcl-std/modules/std-turns)
* [`types`](/docs/kcl-std/modules/std-types) * [`types`](/docs/kcl-std/modules/std-types)

View File

@ -12,8 +12,8 @@ patternCircular2d(
@sketchSet: [Sketch], @sketchSet: [Sketch],
instances: number, instances: number,
center: Point2d, center: Point2d,
arcDegrees?: number, arcDegrees: number,
rotateDuplicates?: bool, rotateDuplicates: bool,
useOriginal?: bool, useOriginal?: bool,
): [Sketch] ): [Sketch]
``` ```
@ -27,8 +27,8 @@ patternCircular2d(
| `sketchSet` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch(es) to pattern | Yes | | `sketchSet` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch(es) to pattern | Yes |
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center about which to make the pattern. This is a 2D vector. | Yes | | `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center about which to make the pattern. This is a 2D vector. | Yes |
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360. | No | | `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. Defaults to true. | No | | `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. | Yes |
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,19 +1,19 @@
--- ---
title: "patternTransform2d" title: "patternTransform2d"
subtitle: "Function in std::sketch" subtitle: "Function in std::sketch"
excerpt: "Just like `patternTransform`, but works on 2D sketches not 3D solids." excerpt: "Just like patternTransform, but works on 2D sketches not 3D solids."
layout: manual layout: manual
--- ---
Just like `patternTransform`, but works on 2D sketches not 3D solids. Just like patternTransform, but works on 2D sketches not 3D solids.
```kcl ```kcl
patternTransform2d( patternTransform2d(
@sketches: [Sketch; 1+], @sketches: [Sketch],
instances: number(_), instances: number,
transform: fn(number(_)): { }, transform: FunctionSource,
useOriginal?: boolean, useOriginal?: bool,
): [Sketch; 1+] ): [Sketch]
``` ```
@ -22,14 +22,14 @@ patternTransform2d(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) to duplicate. | Yes | | `sketches` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) to duplicate | Yes |
| `instances` | [`number(_)`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | [`fn(number(_)): { }`](/docs/kcl-std/types/std-types-fn) | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes | | `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `useOriginal` | `boolean` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. | No | | `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns
[`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch)
### Examples ### Examples

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -134,6 +134,8 @@ extrude001 = extrude(sketch001, length = 5)`
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await page.waitForTimeout(1000)
// Ensure badge is present // Ensure badge is present
const codePaneButtonHolder = page.locator('#code-button-holder') const codePaneButtonHolder = page.locator('#code-button-holder')
await expect(codePaneButtonHolder).toContainText('notification', { await expect(codePaneButtonHolder).toContainText('notification', {
@ -156,14 +158,10 @@ extrude001 = extrude(sketch001, length = 5)`
await expect( await expect(
page page
.getByText( .getByText(
'Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis' 'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
) )
.first() .first()
).toBeVisible() ).toBeVisible()
// Make sure ApiError is not on the page.
// This ensures we didn't nest the json
await expect(page.getByText('ApiError')).not.toBeVisible()
}) })
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({ test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
@ -181,7 +179,7 @@ extrude001 = extrude(sketch001, length = 5)`
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
// await scene.settled(cmdBar) await scene.settled(cmdBar)
// Ensure badge is present // Ensure badge is present
const codePaneButtonHolder = page.locator('#code-button-holder') const codePaneButtonHolder = page.locator('#code-button-holder')
@ -235,48 +233,6 @@ extrude001 = extrude(sketch001, length = 5)`
.first() .first()
).toBeVisible() ).toBeVisible()
}) })
test('KCL errors with functions show hints for the entire backtrace', async ({
page,
homePage,
scene,
cmdBar,
editor,
toolbar,
}) => {
await homePage.goToModelingScene()
await scene.settled(cmdBar)
const code = `fn check(@x) {
return assert(x, isGreaterThan = 0)
}
fn middle(@x) {
return check(x)
}
middle(1)
middle(0)
`
await test.step('Set the code with a KCL error', async () => {
await toolbar.openPane('code')
await editor.replaceCode('', code)
})
// This shows all the diagnostics in a way that doesn't require the mouse
// pointer hovering over a coordinate, which would be brittle.
await test.step('Open CodeMirror diagnostics list', async () => {
// Ensure keyboard focus is in the editor.
await page.getByText('fn check(').click()
await page.keyboard.press('ControlOrMeta+Shift+M')
})
await expect(
page.getByText(`assert failed: Expected 0 to be greater than 0 but it wasn't
check()
middle()`)
).toBeVisible()
// There should be one hint inside middle() and one at the top level.
await expect(page.getByText('Part of the error backtrace')).toHaveCount(2)
})
}) })
test( test(

View File

@ -36,29 +36,27 @@ test.describe('Command bar tests', () => {
await u.closeDebugPanel() await u.closeDebugPanel()
// Click the line of code for xLine. // Click the line of code for xLine.
await page.getByText(`startProfile(at = [-10, -10])`).click() await page.getByText(`close()`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
await page.waitForTimeout(200)
await toolbar.extrudeButton.click() await toolbar.extrudeButton.click()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
commandName: 'Extrude', commandName: 'Extrude',
currentArgKey: 'length', currentArgKey: 'sketches',
currentArgValue: '5', currentArgValue: '',
headerArguments: { headerArguments: {
Profiles: '1 profile', Sketches: '',
Length: '', Length: '',
}, },
highlightedHeaderArg: 'length', highlightedHeaderArg: 'sketches',
}) })
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
commandName: 'Extrude', commandName: 'Extrude',
headerArguments: { headerArguments: {
Profiles: '1 profile', Sketches: '1 segment',
Length: '5', Length: '5',
}, },
}) })
@ -288,7 +286,7 @@ test.describe('Command bar tests', () => {
await cmdBar.cmdOptions.getByText('Extrude').click() await cmdBar.cmdOptions.getByText('Extrude').click()
// Assert that we're on the selection step // Assert that we're on the selection step
await expect(page.getByRole('button', { name: 'Profiles' })).toBeDisabled() await expect(page.getByRole('button', { name: 'sketches' })).toBeDisabled()
// Select a face // Select a face
await page.mouse.move(700, 200) await page.mouse.move(700, 200)
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
@ -401,6 +399,7 @@ test.describe('Command bar tests', () => {
sortBy: 'last-modified-desc', sortBy: 'last-modified-desc',
}) })
await page.goto(page.url() + targetURL) await page.goto(page.url() + targetURL)
expect(page.url()).toContain(targetURL)
}) })
await test.step(`Submit the command`, async () => { await test.step(`Submit the command`, async () => {
@ -411,7 +410,7 @@ test.describe('Command bar tests', () => {
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Method: '', Method: '',
Name: 'main.kcl', Name: 'test',
Code: '1 line', Code: '1 line',
}, },
highlightedHeaderArg: 'method', highlightedHeaderArg: 'method',
@ -422,7 +421,7 @@ test.describe('Command bar tests', () => {
commandName: 'Import file from URL', commandName: 'Import file from URL',
headerArguments: { headerArguments: {
Method: 'New project', Method: 'New project',
Name: 'main.kcl', Name: 'test',
Code: '1 line', Code: '1 line',
}, },
}) })
@ -464,6 +463,7 @@ test.describe('Command bar tests', () => {
sortBy: 'last-modified-desc', sortBy: 'last-modified-desc',
}) })
await page.goto(page.url() + targetURL) await page.goto(page.url() + targetURL)
expect(page.url()).toContain(targetURL)
}) })
await test.step(`Submit the command`, async () => { await test.step(`Submit the command`, async () => {
@ -474,7 +474,7 @@ test.describe('Command bar tests', () => {
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Method: '', Method: '',
Name: 'main.kcl', Name: 'test',
Code: '1 line', Code: '1 line',
}, },
highlightedHeaderArg: 'method', highlightedHeaderArg: 'method',
@ -487,7 +487,7 @@ test.describe('Command bar tests', () => {
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Method: 'Existing project', Method: 'Existing project',
Name: 'main.kcl', Name: 'test',
ProjectName: '', ProjectName: '',
Code: '1 line', Code: '1 line',
}, },
@ -500,7 +500,7 @@ test.describe('Command bar tests', () => {
headerArguments: { headerArguments: {
Method: 'Existing project', Method: 'Existing project',
ProjectName: 'testProjectDir', ProjectName: 'testProjectDir',
Name: 'main.kcl', Name: 'test',
Code: '1 line', Code: '1 line',
}, },
}) })
@ -510,7 +510,7 @@ test.describe('Command bar tests', () => {
await test.step(`Ensure we created the project and are in the modeling scene`, async () => { await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
await editor.expectEditor.toContain('extrusionDistance = 12') await editor.expectEditor.toContain('extrusionDistance = 12')
await toolbar.openPane('files') await toolbar.openPane('files')
await toolbar.expectFileTreeState(['main-1.kcl', 'main.kcl']) await toolbar.expectFileTreeState(['main.kcl', 'test.kcl'])
}) })
}) })
@ -661,27 +661,4 @@ c = 3 + a`
`a = 5b = a * amyParameter001 = ${newValue}c = 3 + a` `a = 5b = a * amyParameter001 = ${newValue}c = 3 + a`
) )
}) })
test('Command palette can be opened via query parameter', async ({
page,
homePage,
cmdBar,
}) => {
await page.goto(`${page.url()}/?cmd=app.theme&groupId=settings`)
await homePage.expectState({
projectCards: [],
sortBy: 'last-modified-desc',
})
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Settings · app · theme',
currentArgKey: 'value',
currentArgValue: '',
headerArguments: {
Level: 'user',
Value: '',
},
highlightedHeaderArg: 'value',
})
})
}) })

View File

@ -58,6 +58,12 @@ test(
await expect(submitButton).toBeVisible() await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Look out for the toast message
const exportingToastMessage = page.getByText(`Exporting...`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
await expect(exportingToastMessage).toBeVisible()
await expect(alreadyExportingToastMessage).not.toBeVisible()
// Expect it to succeed // Expect it to succeed
const errorToastMessage = page.getByText(`Error while exporting`) const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`) const engineErrorToastMessage = page.getByText(`Nothing to export`)
@ -65,9 +71,8 @@ test(
await expect(engineErrorToastMessage).not.toBeVisible() await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`) const successToastMessage = page.getByText(`Exported successfully`)
await page.waitForTimeout(1_000) await expect(successToastMessage).toBeVisible()
const count = await successToastMessage.count() await expect(exportingToastMessage).not.toBeVisible()
await expect(count).toBeGreaterThanOrEqual(1)
// Check for the exported file // Check for the exported file
const firstFileFullPath = path.resolve( const firstFileFullPath = path.resolve(
@ -136,9 +141,7 @@ test(
await expect(engineErrorToastMessage).not.toBeVisible() await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`) const successToastMessage = page.getByText(`Exported successfully`)
await page.waitForTimeout(1_000) await expect(successToastMessage).toBeVisible()
const count = await successToastMessage.count()
await expect(count).toBeGreaterThanOrEqual(1)
await expect(exportingToastMessage).not.toBeVisible() await expect(exportingToastMessage).not.toBeVisible()
// Check for the exported file= // Check for the exported file=

View File

@ -1131,15 +1131,14 @@ sketch001 = startSketchOn(XZ)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.getByText('startProfile(at = [4.61, -14.01])').click() await page.getByText('startProfile(at = [4.61, -14.01])').click()
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
await page.waitForTimeout(200)
await toolbar.extrudeButton.click() await toolbar.extrudeButton.click()
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'length',
currentArgValue: '5', currentArgValue: '5',
headerArguments: { headerArguments: {
Profiles: '1 profile', Sketches: '1 face',
Length: '', Length: '',
}, },
highlightedHeaderArg: 'length', highlightedHeaderArg: 'length',
@ -1149,7 +1148,7 @@ sketch001 = startSketchOn(XZ)
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
Profiles: '1 profile', Sketches: '1 face',
Length: '5', Length: '5',
}, },
commandName: 'Extrude', commandName: 'Extrude',
@ -1355,9 +1354,7 @@ sketch001 = startSketchOn(XZ)
const u = await getUtils(page) const u = await getUtils(page)
const projectLink = page.getByRole('link', { name: 'cube' }) const projectLink = page.getByRole('link', { name: 'cube' })
const gizmo = page.locator('[aria-label*=gizmo]') const gizmo = page.locator('[aria-label*=gizmo]')
const resetCameraButton = page.getByRole('button', { const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
name: 'Reset view',
})
const locationToHaveColor = async ( const locationToHaveColor = async (
position: { x: number; y: number }, position: { x: number; y: number },
color: [number, number, number] color: [number, number, number]
@ -1536,6 +1533,7 @@ sketch001 = startSketchOn(XZ)
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.connectionEstablished() await scene.connectionEstablished()
await scene.settled(cmdBar)
await scene.expectPixelColor( await scene.expectPixelColor(
TEST_COLORS.DARK_MODE_BKGD, TEST_COLORS.DARK_MODE_BKGD,

View File

@ -146,19 +146,15 @@ export class CmdBarFixture {
await this.cmdBarOpenBtn.click() await this.cmdBarOpenBtn.click()
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible() await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
if (selectCmd === 'promptToEdit') { if (selectCmd === 'promptToEdit') {
const promptEditCommand = this.selectOption({ name: 'Text-to-CAD Edit' }) const promptEditCommand = this.page.getByText(
'Use Zoo AI to edit your parts and code.'
)
await expect(promptEditCommand.first()).toBeVisible() await expect(promptEditCommand.first()).toBeVisible()
await promptEditCommand.first().scrollIntoViewIfNeeded() await promptEditCommand.first().scrollIntoViewIfNeeded()
await promptEditCommand.first().click() await promptEditCommand.first().click()
} }
} }
closeCmdBar = async () => {
const cmdBarCloseBtn = this.page.getByTestId('command-bar-close-button')
await cmdBarCloseBtn.click()
await expect(this.cmdBarElement).not.toBeVisible()
}
get cmdSearchInput() { get cmdSearchInput() {
return this.page.getByTestId('cmd-bar-search') return this.page.getByTestId('cmd-bar-search')
} }
@ -302,27 +298,4 @@ export class CmdBarFixture {
`Monitoring text-to-cad API requests. Output will be saved to: ${outputPath}` `Monitoring text-to-cad API requests. Output will be saved to: ${outputPath}`
) )
} }
async toBeOpened() {
// Check that the command bar is opened
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
}
async expectArgValue(value: string) {
// Check the placeholder project name exists
const actualArgument = await this.cmdBarElement
.getByTestId('cmd-bar-arg-value')
.inputValue()
const expectedArgument = value
expect(actualArgument).toBe(expectedArgument)
}
async expectCommandName(value: string) {
// Check the placeholder project name exists
const actual = await this.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = value
expect(actual).toBe(expected)
}
} }

View File

@ -24,7 +24,6 @@ export class HomePageFixture {
projectTextName!: Locator projectTextName!: Locator
sortByDateBtn!: Locator sortByDateBtn!: Locator
sortByNameBtn!: Locator sortByNameBtn!: Locator
appHeader!: Locator
tutorialBtn!: Locator tutorialBtn!: Locator
constructor(page: Page) { constructor(page: Page) {
@ -45,7 +44,6 @@ export class HomePageFixture {
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified') this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name') this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
this.appHeader = this.page.getByTestId('app-header')
this.tutorialBtn = this.page.getByTestId('home-tutorial-button') this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
} }
@ -121,19 +119,10 @@ export class HomePageFixture {
await projectCard.click() await projectCard.click()
} }
/** Returns the project name in case caller has used the default and needs it */ goToModelingScene = async (name: string = 'testDefault') => {
goToModelingScene = async (name = 'testDefault') => {
// On web this is a no-op. There is no project view. // On web this is a no-op. There is no project view.
if (process.env.PLATFORM === 'web') return '' if (process.env.PLATFORM === 'web') return
await this.createAndGoToProject(name) await this.createAndGoToProject(name)
return name
}
isNativeFileMenuCreated = async () => {
await expect(this.appHeader).toHaveAttribute(
'data-native-file-menu',
'true'
)
} }
} }

View File

@ -46,7 +46,6 @@ export class SceneFixture {
public networkToggleConnected!: Locator public networkToggleConnected!: Locator
public engineConnectionsSpinner!: Locator public engineConnectionsSpinner!: Locator
public startEditSketchBtn!: Locator public startEditSketchBtn!: Locator
public appHeader!: Locator
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
@ -58,7 +57,6 @@ export class SceneFixture {
this.startEditSketchBtn = page this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' }) .getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' })) .or(page.getByRole('button', { name: 'Edit Sketch' }))
this.appHeader = this.page.getByTestId('app-header')
} }
private _serialiseScene = async (): Promise<SceneSerialised> => { private _serialiseScene = async (): Promise<SceneSerialised> => {
const camera = await this.getCameraInfo() const camera = await this.getCameraInfo()
@ -282,13 +280,6 @@ export class SceneFixture {
await expect(buttonToTest).toBeVisible() await expect(buttonToTest).toBeVisible()
await buttonToTest.click() await buttonToTest.click()
} }
isNativeFileMenuCreated = async () => {
await expect(this.appHeader).toHaveAttribute(
'data-native-file-menu',
'true'
)
}
} }
function isColourArray( function isColourArray(

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@ test.describe('Onboarding tests', () => {
homePage, homePage,
toolbar, toolbar,
editor, editor,
scene,
tronApp, tronApp,
}) => { }) => {
if (!tronApp) { if (!tronApp) {
@ -61,6 +62,7 @@ test.describe('Onboarding tests', () => {
await editor.expectEditor.toContain('@settings(defaultLengthUnit = in)', { await editor.expectEditor.toContain('@settings(defaultLengthUnit = in)', {
shouldNormalise: true, shouldNormalise: true,
}) })
await scene.connectionEstablished()
}) })
await test.step('Go home and verify we still see the tutorial button, then begin it.', async () => { await test.step('Go home and verify we still see the tutorial button, then begin it.', async () => {
@ -130,7 +132,9 @@ test.describe('Onboarding tests', () => {
}) })
await test.step('Dismiss the onboarding', async () => { await test.step('Dismiss the onboarding', async () => {
await postDismissToast.waitFor({ state: 'hidden' })
await page.keyboard.press('Escape') await page.keyboard.press('Escape')
await expect(postDismissToast).toBeVisible()
await expect(page.getByTestId('onboarding-content')).not.toBeVisible() await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect.poll(() => page.url()).not.toContain('/onboarding') await expect.poll(() => page.url()).not.toContain('/onboarding')
}) })
@ -158,10 +162,13 @@ test.describe('Onboarding tests', () => {
await test.step('Gets to the onboarding start', async () => { await test.step('Gets to the onboarding start', async () => {
await expect(toolbar.projectName).toContainText('tutorial-project') await expect(toolbar.projectName).toContainText('tutorial-project')
await expect(tutorialWelcomeHeading).toBeVisible() await expect(tutorialWelcomeHeading).toBeVisible()
await scene.connectionEstablished()
}) })
await test.step('Dismiss the onboarding', async () => { await test.step('Dismiss the onboarding', async () => {
await postDismissToast.waitFor({ state: 'hidden' })
await page.keyboard.press('Escape') await page.keyboard.press('Escape')
await expect(postDismissToast).toBeVisible()
await expect(page.getByTestId('onboarding-content')).not.toBeVisible() await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect.poll(() => page.url()).not.toContain('/onboarding') await expect.poll(() => page.url()).not.toContain('/onboarding')
}) })

View File

@ -74,11 +74,20 @@ test.describe('Point-and-click tests', () => {
await test.step('do extrude flow and check extrude code is added to editor', async () => { await test.step('do extrude flow and check extrude code is added to editor', async () => {
await toolbar.extrudeButton.click() await toolbar.extrudeButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: { Sketches: '', Length: '' },
highlightedHeaderArg: 'sketches',
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'length',
currentArgValue: '5', currentArgValue: '5',
headerArguments: { Profiles: '1 profile', Length: '' }, headerArguments: { Sketches: '1 face', Length: '' },
highlightedHeaderArg: 'length', highlightedHeaderArg: 'length',
commandName: 'Extrude', commandName: 'Extrude',
}) })
@ -89,7 +98,7 @@ test.describe('Point-and-click tests', () => {
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { Profiles: '1 profile', Length: '5' }, headerArguments: { Sketches: '1 face', Length: '5' },
commandName: 'Extrude', commandName: 'Extrude',
}) })
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
@ -1625,15 +1634,15 @@ sketch002 = startSketchOn(plane001)
stage: 'arguments', stage: 'arguments',
currentArgKey: 'sketches', currentArgKey: 'sketches',
currentArgValue: '', currentArgValue: '',
headerArguments: { Profiles: '' }, headerArguments: { Sketches: '' },
highlightedHeaderArg: 'Profiles', highlightedHeaderArg: 'sketches',
commandName: 'Loft', commandName: 'Loft',
}) })
await selectSketches() await selectSketches()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { Profiles: '2 profiles' }, headerArguments: { Sketches: '2 faces' },
commandName: 'Loft', commandName: 'Loft',
}) })
await cmdBar.submit() await cmdBar.submit()
@ -1645,9 +1654,18 @@ sketch002 = startSketchOn(plane001)
await test.step(`Go through the command bar flow with preselected sketches`, async () => { await test.step(`Go through the command bar flow with preselected sketches`, async () => {
await toolbar.loftButton.click() await toolbar.loftButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: { Sketches: '' },
highlightedHeaderArg: 'sketches',
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { Profiles: '2 profiles' }, headerArguments: { Sketches: '2 faces' },
commandName: 'Loft', commandName: 'Loft',
}) })
await cmdBar.submit() await cmdBar.submit()
@ -1812,10 +1830,10 @@ sketch002 = startSketchOn(XZ)
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '', Sectional: '',
Profiles: '', Sketches: '',
Path: '', Path: '',
}, },
highlightedHeaderArg: 'Profiles', highlightedHeaderArg: 'sketches',
stage: 'arguments', stage: 'arguments',
}) })
await clickOnSketch1() await clickOnSketch1()
@ -1826,7 +1844,7 @@ sketch002 = startSketchOn(XZ)
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '', Sectional: '',
Profiles: '1 profile', Sketches: '1 face',
Path: '', Path: '',
}, },
highlightedHeaderArg: 'path', highlightedHeaderArg: 'path',
@ -1839,7 +1857,7 @@ sketch002 = startSketchOn(XZ)
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '', Sectional: '',
Profiles: '1 profile', Sketches: '1 face',
Path: '', Path: '',
}, },
highlightedHeaderArg: 'path', highlightedHeaderArg: 'path',
@ -1849,7 +1867,7 @@ sketch002 = startSketchOn(XZ)
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Sweep', commandName: 'Sweep',
headerArguments: { headerArguments: {
Profiles: '1 profile', Sketches: '1 face',
Path: '1 segment', Path: '1 segment',
Sectional: '', Sectional: '',
}, },
@ -1913,51 +1931,63 @@ sketch002 = startSketchOn(XZ)
}) })
}) })
test(`Sweep point-and-click helix`, async ({ test(`Sweep point-and-click failing validation`, async ({
context, context,
page, page,
homePage, homePage,
scene, scene,
editor,
toolbar, toolbar,
cmdBar, cmdBar,
}) => { }) => {
const circleCode = `circle(sketch001, center = [0, -1], radius = .1)` const initialCode = `@settings(defaultLengthUnit = in)
const initialCode = `helix001 = helix( sketch001 = startSketchOn(YZ)
axis = X, |> circle(
radius = 1, center = [0, 0],
length = 10, radius = 500
revolutions = 10,
angleStart = 0,
ccw = false,
) )
sketch001 = startSketchOn(XZ) sketch002 = startSketchOn(XZ)
profile001 = ${circleCode}` |> startProfile(at = [0, 0])
const sweepDeclaration = 'sweep001 = sweep(profile001, path = helix001)' |> xLine(length = -500)
|> line(endAbsolute = [-2000, 500])
`
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
await test.step(`Add sweep through the command bar flow`, async () => { // One dumb hardcoded screen pixel value
await toolbar.openPane('feature-tree') const testPoint = { x: 700, y: 250 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(
testPoint.x - 50,
testPoint.y
)
await test.step(`Look for sketch001`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
})
await test.step(`Go through the command bar flow and fail validation with a toast`, async () => {
await toolbar.sweepButton.click() await toolbar.sweepButton.click()
await expect
.poll(() => page.getByText('Please select one').count())
.toBe(1)
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Sweep', commandName: 'Sweep',
currentArgKey: 'sketches', currentArgKey: 'sketches',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '', Sectional: '',
Profiles: '', Sketches: '',
Path: '', Path: '',
}, },
highlightedHeaderArg: 'Profiles', highlightedHeaderArg: 'sketches',
stage: 'arguments', stage: 'arguments',
}) })
await editor.scrollToText(circleCode) await clickOnSketch1()
await page.getByText(circleCode).click()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Sweep', commandName: 'Sweep',
@ -1965,45 +1995,17 @@ profile001 = ${circleCode}`
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '', Sectional: '',
Profiles: '1 profile', Sketches: '1 face',
Path: '',
},
highlightedHeaderArg: 'path',
stage: 'arguments',
})
const helix = await toolbar.getFeatureTreeOperation('Helix', 0)
await helix.click()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'path',
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '1 profile',
Path: '', Path: '',
}, },
highlightedHeaderArg: 'path', highlightedHeaderArg: 'path',
stage: 'arguments', stage: 'arguments',
}) })
await clickOnSketch2()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.expectState({ await expect(
commandName: 'Sweep', page.getByText('Unable to sweep with the current selection. Reason:')
headerArguments: { ).toBeVisible()
Profiles: '1 profile',
Path: '1 helix',
Sectional: '',
},
stage: 'review',
})
await cmdBar.progressCmdBar()
await editor.expectEditor.toContain(sweepDeclaration)
})
await test.step('Delete sweep via feature tree selection', async () => {
const sweep = await toolbar.getFeatureTreeOperation('Sweep', 0)
await sweep.click()
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain(sweepDeclaration)
}) })
}) })
@ -2088,6 +2090,18 @@ extrude001 = extrude(sketch001, length = -12)
await test.step(`Apply fillet to the preselected edge`, async () => { await test.step(`Apply fillet to the preselected edge`, async () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await toolbar.filletButton.click() await toolbar.filletButton.click()
await cmdBar.expectState({
commandName: 'Fillet',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Radius: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Fillet', commandName: 'Fillet',
highlightedHeaderArg: 'radius', highlightedHeaderArg: 'radius',
@ -2617,6 +2631,18 @@ extrude001 = extrude(profile001, length = 5)
await test.step(`Apply fillet`, async () => { await test.step(`Apply fillet`, async () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await toolbar.filletButton.click() await toolbar.filletButton.click()
await cmdBar.expectState({
commandName: 'Fillet',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Radius: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Fillet', commandName: 'Fillet',
highlightedHeaderArg: 'radius', highlightedHeaderArg: 'radius',
@ -2722,6 +2748,19 @@ extrude001 = extrude(sketch001, length = -12)
await test.step(`Apply chamfer to the preselected edge`, async () => { await test.step(`Apply chamfer to the preselected edge`, async () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await toolbar.chamferButton.click() await toolbar.chamferButton.click()
await cmdBar.expectState({
commandName: 'Chamfer',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Length: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await page.waitForTimeout(1000)
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Chamfer', commandName: 'Chamfer',
highlightedHeaderArg: 'length', highlightedHeaderArg: 'length',
@ -3205,6 +3244,8 @@ extrude001 = extrude(sketch001, length = 30)
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => { await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
await toolbar.shellButton.click() await toolbar.shellButton.click()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
@ -3586,6 +3627,67 @@ profile001 = startProfile(sketch001, at = [-20, 20])
}) })
}) })
test(`Shell dry-run validation rejects sweeps`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn(YZ)
|> circle(
center = [0, 0],
radius = 500
)
sketch002 = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> xLine(length = -2000)
sweep001 = sweep(sketch001, path = sketch002)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.settled(cmdBar)
// One dumb hardcoded screen pixel value
const testPoint = { x: 500, y: 250 }
const [clickOnSweep] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
await test.step(`Confirm sweep exists`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor([231, 231, 231], testPoint, 15)
})
await test.step(`Go through the Shell flow and fail validation with a toast`, async () => {
await toolbar.shellButton.click()
await expect
.poll(() => page.getByText('Please select one').count())
.toBe(1)
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Thickness: '',
},
highlightedHeaderArg: 'selection',
commandName: 'Shell',
})
await clickOnSweep()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await expect(
page.getByText('Unable to shell with the current selection. Reason:')
).toBeVisible()
await page.waitForTimeout(1000)
})
})
test.describe('Revolve point and click workflows', () => { test.describe('Revolve point and click workflows', () => {
test('Base case workflow, auto spam continue in command bar', async ({ test('Base case workflow, auto spam continue in command bar', async ({
context, context,
@ -3634,17 +3736,16 @@ tag=$rectangleSegmentC002,
await scene.settled(cmdBar) await scene.settled(cmdBar)
// select line of code // select line of code
const codeToSelection = `startProfile(at = [-66.77, 84.81])` const codeToSelection = `segAng(rectangleSegmentA002) - 90,`
// revolve // revolve
await editor.scrollToText(codeToSelection) await editor.scrollToText(codeToSelection)
await page.getByText(codeToSelection).click() await page.getByText(codeToSelection).click()
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
await page.waitForTimeout(200)
await toolbar.revolveButton.click() await toolbar.revolveButton.click()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = X)` const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = X)`
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy() expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
@ -4573,12 +4674,24 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => { await test.step('Go through command bar flow', async () => {
await toolbar.extrudeButton.click() await toolbar.extrudeButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Sketches: '',
Length: '',
},
highlightedHeaderArg: 'sketches',
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'length',
currentArgValue: '5', currentArgValue: '5',
headerArguments: { headerArguments: {
Profiles: '2 profiles', Sketches: '2 faces',
Length: '', Length: '',
}, },
highlightedHeaderArg: 'length', highlightedHeaderArg: 'length',
@ -4589,7 +4702,7 @@ path001 = startProfile(sketch001, at = [0, 0])
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
Profiles: '2 profiles', Sketches: '2 faces',
Length: '1', Length: '1',
}, },
commandName: 'Extrude', commandName: 'Extrude',
@ -4655,12 +4768,25 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => { await test.step('Go through command bar flow', async () => {
await toolbar.sweepButton.click() await toolbar.sweepButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Sketches: '',
Path: '',
Sectional: '',
},
highlightedHeaderArg: 'sketches',
commandName: 'Sweep',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'path', currentArgKey: 'path',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Profiles: '2 profiles', Sketches: '2 faces',
Path: '', Path: '',
Sectional: '', Sectional: '',
}, },
@ -4673,7 +4799,7 @@ path001 = startProfile(sketch001, at = [0, 0])
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
Profiles: '2 profiles', Sketches: '2 faces',
Path: '1 segment', Path: '1 segment',
Sectional: '', Sectional: '',
}, },
@ -4739,12 +4865,25 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => { await test.step('Go through command bar flow', async () => {
await toolbar.closePane('code') await toolbar.closePane('code')
await toolbar.revolveButton.click() await toolbar.revolveButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Sketches: '',
AxisOrEdge: '',
Angle: '',
},
highlightedHeaderArg: 'sketches',
commandName: 'Revolve',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'axisOrEdge', currentArgKey: 'axisOrEdge',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Profiles: '2 profiles', Sketches: '2 faces',
AxisOrEdge: '', AxisOrEdge: '',
Angle: '', Angle: '',
}, },
@ -4760,7 +4899,7 @@ path001 = startProfile(sketch001, at = [0, 0])
currentArgKey: 'angle', currentArgKey: 'angle',
currentArgValue: '360', currentArgValue: '360',
headerArguments: { headerArguments: {
Profiles: '2 profiles', Sketches: '2 faces',
AxisOrEdge: 'Edge', AxisOrEdge: 'Edge',
Edge: '1 segment', Edge: '1 segment',
Angle: '', Angle: '',
@ -4773,7 +4912,7 @@ path001 = startProfile(sketch001, at = [0, 0])
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
Profiles: '2 profiles', Sketches: '2 faces',
AxisOrEdge: 'Edge', AxisOrEdge: 'Edge',
Edge: '1 segment', Edge: '1 segment',
Angle: '180', Angle: '180',
@ -4804,34 +4943,4 @@ path001 = startProfile(sketch001, at = [0, 0])
) )
}) })
}) })
test(`Point and click codemods can't run on KCL errors`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const badCode = `sketch001 = startSketchOn(XZ)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
extrude001 = extrude(profile001 length = 1)`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, badCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.connectionEstablished()
await test.step(`Start Sketch is disabled`, async () => {
await expect(toolbar.startSketchBtn).not.toBeEnabled()
await editor.expectEditor.toContain(badCode, { shouldNormalise: true })
})
await test.step(`Helix is disabled`, async () => {
await expect(toolbar.helixButton).not.toBeEnabled()
await editor.expectEditor.toContain(badCode, { shouldNormalise: true })
})
})
}) })

View File

@ -1818,6 +1818,7 @@ test(
'extrude-custom-plane.kcl', 'extrude-custom-plane.kcl',
'extrude-inside-fn-with-tags.kcl', 'extrude-inside-fn-with-tags.kcl',
'fillet-and-shell.kcl', 'fillet-and-shell.kcl',
'fillet_duplicate_tags.kcl',
'focusrite_scarlett_mounting_bracket.kcl', 'focusrite_scarlett_mounting_bracket.kcl',
'function_sketch.kcl', 'function_sketch.kcl',
'function_sketch_with_position.kcl', 'function_sketch_with_position.kcl',
@ -1825,6 +1826,7 @@ test(
'helix_defaults.kcl', 'helix_defaults.kcl',
'helix_defaults_negative_extrude.kcl', 'helix_defaults_negative_extrude.kcl',
'helix_with_length.kcl', 'helix_with_length.kcl',
'i_shape.kcl',
'kittycad_svg.kcl', 'kittycad_svg.kcl',
'lego.kcl', 'lego.kcl',
'lsystem.kcl', 'lsystem.kcl',

View File

@ -99,8 +99,6 @@ test.describe('edit with AI example snapshots', () => {
await test.step('fire off edit prompt', async () => { await test.step('fire off edit prompt', async () => {
await cmdBar.captureTextToCadRequestSnapshot(test.info()) await cmdBar.captureTextToCadRequestSnapshot(test.info())
await cmdBar.openCmdBar('promptToEdit') await cmdBar.openCmdBar('promptToEdit')
await page.waitForTimeout(100)
await cmdBar.progressCmdBar()
// being specific about the color with a hex means asserting pixel color is more stable // being specific about the color with a hex means asserting pixel color is more stable
await page await page
.getByTestId('cmd-bar-arg-value') .getByTestId('cmd-bar-arg-value')

View File

@ -88,8 +88,6 @@ test.describe('Prompt-to-edit tests', () => {
await test.step('fire off edit prompt', async () => { await test.step('fire off edit prompt', async () => {
await cmdBar.openCmdBar('promptToEdit') await cmdBar.openCmdBar('promptToEdit')
await page.waitForTimeout(100)
await cmdBar.progressCmdBar()
// being specific about the color with a hex means asserting pixel color is more stable // being specific about the color with a hex means asserting pixel color is more stable
await page await page
.getByTestId('cmd-bar-arg-value') .getByTestId('cmd-bar-arg-value')
@ -167,8 +165,6 @@ test.describe('Prompt-to-edit tests', () => {
await test.step('fire of bad prompt', async () => { await test.step('fire of bad prompt', async () => {
await cmdBar.openCmdBar('promptToEdit') await cmdBar.openCmdBar('promptToEdit')
await page.waitForTimeout(100)
await cmdBar.progressCmdBar()
await page await page
.getByTestId('cmd-bar-arg-value') .getByTestId('cmd-bar-arg-value')
.fill('ansheusha asnthuatshoeuhtaoetuhthaeu laughs in dvorak') .fill('ansheusha asnthuatshoeuhtaoetuhthaeu laughs in dvorak')

View File

@ -19,12 +19,11 @@ test.describe('Regression tests', () => {
context, context,
page, page,
homePage, homePage,
scene,
}) => { }) => {
// because the model has `line([0,0]..` it is valid code, but the model is invalid // because the model has `line([0,0]..` it is valid code, but the model is invalid
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251 // regression test for https://github.com/KittyCAD/modeling-app/issues/3251
// Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics // Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
// const u = await getUtils(page) const u = await getUtils(page)
await context.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -41,8 +40,7 @@ test.describe('Regression tests', () => {
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.connectionEstablished() await u.waitForPageLoad()
// await u.waitForPageLoad()
// error in guter // error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible() await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
@ -53,11 +51,8 @@ test.describe('Regression tests', () => {
// the close doesn't work // the close doesn't work
// when https://github.com/KittyCAD/modeling-app/issues/3268 is closed // when https://github.com/KittyCAD/modeling-app/issues/3268 is closed
// this test will need updating // this test will need updating
const crypticErrorText = `Cannot close a path that is non-planar or with duplicate vertices. const crypticErrorText = `ApiError`
Internal engine error on request`
await expect(page.getByText(crypticErrorText).first()).toBeVisible() await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// Ensure we didn't nest the json.
await expect(page.getByText('ApiError')).not.toBeVisible()
}) })
test('user should not have to press down twice in cmdbar', async ({ test('user should not have to press down twice in cmdbar', async ({
page, page,
@ -190,8 +185,8 @@ extrude001 = extrude(sketch001, length = 50)
page.locator('.pretty-json-container >> text=myVar:"67') page.locator('.pretty-json-container >> text=myVar:"67')
).toBeVisible() ).toBeVisible()
}) })
test('ProgramMemory can be serialised', async ({ page, homePage, scene }) => { test('ProgramMemory can be serialised', async ({ page, homePage }) => {
// const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -216,12 +211,11 @@ extrude001 = extrude(sketch001, length = 50)
// Listen for all console events and push the message text to an array // Listen for all console events and push the message text to an array
page.on('console', (message) => messages.push(message.text())) page.on('console', (message) => messages.push(message.text()))
await homePage.goToModelingScene() await homePage.goToModelingScene()
// await u.waitForPageLoad() await u.waitForPageLoad()
await scene.connectionEstablished()
// wait for execution done // wait for execution done
// await u.openDebugPanel() await u.openDebugPanel()
// await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
const forbiddenMessages = ['cannot serialize tagged newtype variant'] const forbiddenMessages = ['cannot serialize tagged newtype variant']
forbiddenMessages.forEach((forbiddenMessage) => { forbiddenMessages.forEach((forbiddenMessage) => {
@ -235,7 +229,6 @@ extrude001 = extrude(sketch001, length = 50)
context, context,
page, page,
homePage, homePage,
scene,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
// const PUR = 400 / 37.5 //pixeltoUnitRatio // const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -254,10 +247,11 @@ extrude001 = extrude(sketch001, length = 50)
shell(exampleSketch, faces = ['end'], thickness = 0.25)` shell(exampleSketch, faces = ['end'], thickness = 0.25)`
) )
}) })
await homePage.goToModelingScene()
await scene.connectionEstablished()
await expect(async () => { await expect(async () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
// error in guter // error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({ await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
timeout: 1_000, timeout: 1_000,
@ -335,10 +329,10 @@ extrude002 = extrude(profile002, length = 150)
) )
const websocketPromise = page.waitForEvent('websocket') const websocketPromise = page.waitForEvent('websocket')
await toolbar.closePane('code')
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await toolbar.closePane('code')
const websocket = await websocketPromise const websocket = await websocketPromise
await scene.connectionEstablished() await scene.connectionEstablished()
@ -458,10 +452,12 @@ extrude002 = extrude(profile002, length = 150)
// Click the stl. // Click the stl.
await expect(stlOption).toBeVisible() await expect(stlOption).toBeVisible()
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Click the checkbox // Click the checkbox
await expect(submitButton).toBeVisible() await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Find the toast. // Find the toast.
@ -469,13 +465,11 @@ extrude002 = extrude(profile002, length = 150)
await expect(exportingToastMessage).toBeVisible() await expect(exportingToastMessage).toBeVisible()
// Expect it to succeed. // Expect it to succeed.
await expect(exportingToastMessage).not.toBeVisible() await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
await expect(engineErrorToastMessage).not.toBeVisible() await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`) const successToastMessage = page.getByText(`Exported successfully`)
await page.waitForTimeout(1_000) await expect(successToastMessage).toBeVisible()
const count = await successToastMessage.count()
await expect(count).toBeGreaterThanOrEqual(1)
} }
) )
// We updated this test such that you can have multiple exports going at once. // We updated this test such that you can have multiple exports going at once.
@ -551,49 +545,10 @@ extrude002 = extrude(profile002, length = 150)
expect(alreadyExportingToastMessage).not.toBeVisible(), expect(alreadyExportingToastMessage).not.toBeVisible(),
]) ])
const count = await successToastMessage.count() await expect(successToastMessage).toHaveCount(2)
await expect(count).toBeGreaterThanOrEqual(2)
}) })
}) })
test(
`Network health indicator only appears in modeling view`,
{ tag: '@electron' },
async ({ context, page }) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
// Locators
const projectsHeading = page.getByRole('heading', {
name: 'Projects',
})
const projectLink = page.getByRole('link', { name: 'bracket' })
const networkHealthIndicator = page.getByTestId('network-toggle')
await test.step('Check the home page', async () => {
await expect(projectsHeading).toBeVisible()
await expect(projectLink).toBeVisible()
await expect(networkHealthIndicator).not.toBeVisible()
})
await test.step('Open the project', async () => {
await projectLink.click()
})
await test.step('Check the modeling view', async () => {
await expect(projectsHeading).not.toBeVisible()
await expect(networkHealthIndicator).toBeVisible()
})
}
)
test(`View gizmo stays visible even when zoomed out all the way`, async ({ test(`View gizmo stays visible even when zoomed out all the way`, async ({
page, page,
homePage, homePage,

View File

@ -995,8 +995,8 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel() await u.closeDebugPanel()
// click profile in code // click "line(end = [1.32, 0.38])"
await page.getByText(`startProfile(at = [-0.45, 0.87])`).click() await page.getByText(`line(end = [1.32, 0.38])`).click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled( await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
{ timeout: 10_000 } { timeout: 10_000 }
@ -1014,13 +1014,14 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
// click extrude // click extrude
await toolbar.extrudeButton.click() await toolbar.extrudeButton.click()
// sketch selection should already have been made. // sketch selection should already have been made. "Sketches: 1 face" only show up when the selection has been made already
// otherwise the cmdbar would be waiting for a selection. // otherwise the cmdbar would be waiting for a selection.
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'length',
currentArgValue: '5', currentArgValue: '5',
headerArguments: { Profiles: '1 profile', Length: '' }, headerArguments: { Sketches: '1 segment', Length: '' },
highlightedHeaderArg: 'length', highlightedHeaderArg: 'length',
commandName: 'Extrude', commandName: 'Extrude',
}) })
@ -1364,18 +1365,18 @@ solid001 = subtract([extrude001], tools = [extrude002])
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`fn in2mm(@inches) { `fn in2mm = (inches) => {
return inches * 25.4 return inches * 25.4
} }
railTop = in2mm(.748) const railTop = in2mm(.748)
railSide = in2mm(.024) const railSide = in2mm(.024)
railBaseWidth = in2mm(.612) const railBaseWidth = in2mm(.612)
railWideWidth = in2mm(.835) const railWideWidth = in2mm(.835)
railBaseLength = in2mm(.200) const railBaseLength = in2mm(.200)
railClampable = in2mm(.200) const railClampable = in2mm(.200)
rail = startSketchOn(XZ) const rail = startSketchOn(XZ)
|> startProfile(at = [-railTop / 2, railClampable + railBaseLength]) |> startProfile(at = [-railTop / 2, railClampable + railBaseLength])
|> line(endAbsolute = [ |> line(endAbsolute = [
railTop / 2, railTop / 2,
@ -3495,69 +3496,6 @@ profile001 = startProfile(sketch001, at = [-102.72, 237.44])
).toBeVisible() ).toBeVisible()
}) })
// Ensure feature tree is not showing previous file's content when switching to a file with KCL errors.
test('Feature tree shows correct sketch count per file', async ({
context,
homePage,
scene,
toolbar,
cmdBar,
page,
}) => {
const u = await getUtils(page)
// Setup project with files.
const GOOD_KCL = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [220.81, 253.8])
|> line(end = [132.84, -151.31])
|> line(end = [25.51, 167.15])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(XZ)
profile002 = startProfile(sketch002, at = [158.35, -70.82])
|> line(end = [73.9, -152.19])
|> line(end = [85.33, 135.48])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`
const ERROR_KCL = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [127.56, 179.02])
|> line(end = [132.84, -112.6])
|> line(end = [85.33, 234.01])
|> line(enfd = [-137.23, -54.55])`
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, 'multi-file-sketch-test')
await fs.mkdir(projectDir, { recursive: true })
await Promise.all([
fs.writeFile(path.join(projectDir, 'good.kcl'), GOOD_KCL, 'utf-8'),
fs.writeFile(path.join(projectDir, 'error.kcl'), ERROR_KCL, 'utf-8'),
])
})
await page.setBodyDimensions({ width: 1000, height: 800 })
await homePage.openProject('multi-file-sketch-test')
await scene.connectionEstablished()
await u.closeDebugPanel()
await toolbar.openFeatureTreePane()
await toolbar.openPane('files')
await toolbar.openFile('good.kcl')
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(2)
await toolbar.openFile('error.kcl')
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(0)
})
test('adding a syntax error, recovers after fixing', async ({ test('adding a syntax error, recovers after fixing', async ({
page, page,
homePage, homePage,

View File

@ -1,3 +1,4 @@
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture' import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture' import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates' import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
@ -8,7 +9,6 @@ import {
settingsToToml, settingsToToml,
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
test.beforeEach(async ({ page, context }) => { test.beforeEach(async ({ page, context }) => {
// Make the user avatar image always 404 // Make the user avatar image always 404
@ -766,7 +766,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
}) })
}) })
test('theme persists', async ({ page, context, homePage }) => { test('theme persists', async ({ page, context }) => {
const u = await getUtils(page) const u = await getUtils(page)
await context.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
@ -784,7 +784,7 @@ test('theme persists', async ({ page, context, homePage }) => {
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await homePage.goToModelingScene() await u.waitForAuthSkipAppStart()
await page.waitForTimeout(500) await page.waitForTimeout(500)
// await page.getByRole('link', { name: 'Settings Settings (tooltip)' }).click() // await page.getByRole('link', { name: 'Settings Settings (tooltip)' }).click()
@ -812,7 +812,7 @@ test('theme persists', async ({ page, context, homePage }) => {
// Disconnect and reconnect to check the theme persists through a reload // Disconnect and reconnect to check the theme persists through a reload
// Expect the network to be down // Expect the network to be down
await expect(networkToggle).toContainText('Problem') await expect(networkToggle).toContainText('Offline')
// simulate network up // simulate network up
await u.emulateNetworkConditions({ await u.emulateNetworkConditions({
@ -873,50 +873,6 @@ sweepSketch = startSketchOn(XY)
mask: lowerRightMasks(page), mask: lowerRightMasks(page),
}) })
}) })
test('code color goober works with single quotes', async ({
page,
context,
scene,
cmdBar,
}) => {
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`// Create a pipe using a sweep.
// Create a path for the sweep.
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
sweepSketch = startSketchOn(XY)
|> startProfile(at = [2, 0])
|> arc(angleStart = 0, angleEnd = 360, radius = 2)
|> sweep(path = sweepPath)
|> appearance(
color = '#bb00ff',
metalness = 90,
roughness = 90
)
`
)
})
await page.setViewportSize({ width: 1200, height: 1000 })
await u.waitForAuthSkipAppStart()
await scene.settled(cmdBar)
await expect(page, 'expect small color widget').toHaveScreenshot({
maxDiffPixels: 100,
mask: lowerRightMasks(page),
})
})
test('code color goober opening window', async ({ test('code color goober opening window', async ({
page, page,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@ -1,22 +1,19 @@
import type { EngineCommand } from '@src/lang/std/artifactGraph' import type { EngineCommand } from '@src/lang/std/artifactGraph'
import { uuidv4 } from '@src/lib/utils' import { uuidv4 } from '@src/lib/utils'
import { import { commonPoints, getUtils } from '@e2e/playwright/test-utils'
commonPoints,
getUtils,
TEST_COLORS,
circleMove,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Test network related behaviors', () => { test.describe(
'Test network and connection issues',
{
tag: ['@macos', '@windows'],
},
() => {
test( test(
'simulate network down and network little widget', 'simulate network down and network little widget',
{ tag: '@skipLocalEngine' }, { tag: '@skipLocalEngine' },
async ({ page, homePage }) => { async ({ page, homePage }) => {
const networkToggleConnectedText = page.getByText('Connected')
const networkToggleWeakText = page.getByText('Network health (Weak)')
const u = await getUtils(page) const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -37,9 +34,7 @@ test.describe('Test network related behaviors', () => {
await expect(networkPopover).not.toBeVisible() await expect(networkPopover).not.toBeVisible()
// (First check) Expect the network to be up // (First check) Expect the network to be up
await expect( await expect(networkToggle).toContainText('Connected')
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
// Click the network widget // Click the network widget
await networkWidget.click() await networkWidget.click()
@ -87,9 +82,7 @@ test.describe('Test network related behaviors', () => {
).not.toBeDisabled({ timeout: 15000 }) ).not.toBeDisabled({ timeout: 15000 })
// (Second check) expect the network to be up // (Second check) expect the network to be up
await expect( await expect(networkToggle).toContainText('Connected')
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
} }
) )
@ -98,8 +91,6 @@ test.describe('Test network related behaviors', () => {
{ tag: '@skipLocalEngine' }, { tag: '@skipLocalEngine' },
async ({ page, homePage, toolbar, scene, cmdBar }) => { async ({ page, homePage, toolbar, scene, cmdBar }) => {
const networkToggle = page.getByTestId('network-toggle') const networkToggle = page.getByTestId('network-toggle')
const networkToggleConnectedText = page.getByText('Connected')
const networkToggleWeakText = page.getByText('Network health (Weak)')
const u = await getUtils(page) const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -118,7 +109,7 @@ test.describe('Test network related behaviors', () => {
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)` `sketch001 = startSketchOn(XZ)`
) )
await u.closeDebugPanel() await u.closeDebugPanel()
@ -127,7 +118,7 @@ test.describe('Test network related behaviors', () => {
const startXPx = 600 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfile(sketch001, at = ${commonPoints.startAt})` `sketch001 = startSketchOn(XZ)profile001 = startProfile(sketch001, at = ${commonPoints.startAt})`
) )
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -136,14 +127,11 @@ test.describe('Test network related behaviors', () => {
await expect( await expect(
page.locator('.cm-content') page.locator('.cm-content')
).toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfile(sketch001, at = ${commonPoints.startAt}) ).toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfile(sketch001, at = ${commonPoints.startAt})
|> xLine(length = ${commonPoints.num1})`) |> xLine(length = ${commonPoints.num1})`)
// Expect the network to be up // Expect the network to be up
await networkToggle.hover() await expect(networkToggle).toContainText('Connected')
await expect(
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
// simulate network down // simulate network down
await u.emulateNetworkConditions({ await u.emulateNetworkConditions({
@ -155,7 +143,6 @@ test.describe('Test network related behaviors', () => {
}) })
// Expect the network to be down // Expect the network to be down
await networkToggle.hover()
await expect(networkToggle).toContainText('Problem') await expect(networkToggle).toContainText('Problem')
// Ensure we are not in sketch mode // Ensure we are not in sketch mode
@ -181,11 +168,7 @@ test.describe('Test network related behaviors', () => {
).not.toBeDisabled({ timeout: 15000 }) ).not.toBeDisabled({ timeout: 15000 })
// Expect the network to be up // Expect the network to be up
await networkToggle.hover() await expect(networkToggle).toContainText('Connected')
await expect(
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
await scene.settled(cmdBar) await scene.settled(cmdBar)
// Click off the code pane. // Click off the code pane.
@ -200,7 +183,9 @@ test.describe('Test network related behaviors', () => {
await toolbar.editSketch() await toolbar.editSketch()
// Click the line tool // Click the line tool
await page.getByRole('button', { name: 'line Line', exact: true }).click() await page
.getByRole('button', { name: 'line Line', exact: true })
.click()
await page.waitForTimeout(150) await page.waitForTimeout(150)
@ -234,10 +219,7 @@ test.describe('Test network related behaviors', () => {
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect await expect
.poll(u.normalisedEditorCode) .poll(u.normalisedEditorCode)
.toBe(`@settings(defaultLengthUnit = in) .toBe(`sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [12.34, -12.34]) profile001 = startProfile(sketch001, at = [12.34, -12.34])
|> xLine(length = 12.34) |> xLine(length = 12.34)
|> line(end = [-12.34, 12.34]) |> line(end = [-12.34, 12.34])
@ -248,10 +230,7 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34])
await expect await expect
.poll(u.normalisedEditorCode) .poll(u.normalisedEditorCode)
.toBe(`@settings(defaultLengthUnit = in) .toBe(`sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [12.34, -12.34]) profile001 = startProfile(sketch001, at = [12.34, -12.34])
|> xLine(length = 12.34) |> xLine(length = 12.34)
|> line(end = [-12.34, 12.34]) |> line(end = [-12.34, 12.34])
@ -276,88 +255,5 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34])
).not.toBeVisible() ).not.toBeVisible()
} }
) )
test(
'Paused stream freezes view frame, unpause reconnect is seamless to user',
{ tag: ['@electron', '@skipLocalEngine'] },
async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => {
const networkToggle = page.getByTestId('network-toggle')
const networkToggleConnectedText = page.getByText('Connected')
const networkToggleWeakText = page.getByText('Network health (Weak)')
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
stream_idle_mode: 5000,
},
})
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`sketch001 = startSketchOn(XY)
profile001 = startProfile(sketch001, at = [0.0, 0.0])
|> line(end = [10.0, 0])
|> line(end = [0, 10.0])
|> close()`
)
})
const dim = { width: 1200, height: 500 }
await page.setBodyDimensions(dim)
await test.step('Go to modeling scene', async () => {
await homePage.goToModelingScene()
await scene.settled(cmdBar)
})
await test.step('Verify pausing behavior', async () => {
// Wait 5s + 1s to pause.
await page.waitForTimeout(6000)
// We should now be paused. To the user, it should appear we're still
// connected.
await networkToggle.hover()
await expect(
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
const center = {
x: dim.width / 2,
y: dim.height / 2,
}
let probe = { x: 0, y: 0 }
// ... and the model's still visibly there
probe.x = center.x + dim.width / 100
probe.y = center.y
await scene.expectPixelColor(TEST_COLORS.GREY, probe, 15)
probe = { ...center }
// Now move the mouse around to unpause!
await circleMove(page, probe.x, probe.y, 20, 10)
// ONCE AGAIN! Check the view area hasn't changed at all.
// Check the pixel a couple times as it reconnects.
// NOTE: Remember, idle behavior is still on at this point -
// if this test takes longer than 5s shit WILL go south!
probe.x = center.x + dim.width / 100
probe.y = center.y
await scene.expectPixelColor(TEST_COLORS.GREY, probe, 15)
await page.waitForTimeout(1000)
await scene.expectPixelColor(TEST_COLORS.GREY, probe, 15)
probe = { ...center }
// Ensure we're still connected
await networkToggle.hover()
await expect(
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
})
} }
) )
})

View File

@ -21,7 +21,6 @@ export const token = process.env.token || ''
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration' import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist' import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates' import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
import { test } from '@e2e/playwright/zoo-test' import { test } from '@e2e/playwright/zoo-test'
@ -44,8 +43,6 @@ export const lowerRightMasks = (page: Page) => [
export type TestColor = [number, number, number] export type TestColor = [number, number, number]
export const TEST_COLORS: { [key: string]: TestColor } = { export const TEST_COLORS: { [key: string]: TestColor } = {
WHITE: [249, 249, 249], WHITE: [249, 249, 249],
OFFWHITE: [237, 237, 237],
GREY: [142, 142, 142],
YELLOW: [255, 255, 0], YELLOW: [255, 255, 0],
BLUE: [0, 0, 255], BLUE: [0, 0, 255],
DARK_MODE_BKGD: [27, 27, 27], DARK_MODE_BKGD: [27, 27, 27],
@ -551,6 +548,11 @@ export async function getUtils(page: Page, test_?: typeof test) {
createNewFile: async (name: string) => { createNewFile: async (name: string) => {
return test?.step(`Create a file named ${name}`, async () => { return test?.step(`Create a file named ${name}`, async () => {
// If the application is in the middle of connecting a stream
// then creating a new file won't work in the end.
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.getByTestId('create-file-button').click() await page.getByTestId('create-file-button').click()
await page.getByTestId('tree-input-field').fill(name) await page.getByTestId('tree-input-field').fill(name)
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
@ -807,6 +809,8 @@ export const doExport = async (
await page.getByRole('button', { name: 'Submit command' }).click() await page.getByRole('button', { name: 'Submit command' }).click()
// This usually happens immediately after. If we're too slow we don't
// catch it.
await expect(page.getByText('Exported successfully')).toBeVisible() await expect(page.getByText('Exported successfully')).toBeVisible()
if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') { if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') {
@ -1147,77 +1151,3 @@ export function perProjectSettingsToToml(
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
return TOML.stringify(settings as any) return TOML.stringify(settings as any)
} }
export async function clickElectronNativeMenuById(
tronApp: ElectronZoo,
menuId: string
) {
const clickWasTriggered = await tronApp.electron.evaluate(
async ({ app }, menuId) => {
if (!app || !app.applicationMenu) {
return false
}
const menu = app.applicationMenu.getMenuItemById(menuId)
if (!menu) return false
menu.click()
return true
},
menuId
)
expect(clickWasTriggered).toBe(true)
}
export async function findElectronNativeMenuById(
tronApp: ElectronZoo,
menuId: string
) {
const found = await tronApp.electron.evaluate(async ({ app }, menuId) => {
if (!app || !app.applicationMenu) {
return false
}
const menu = app.applicationMenu.getMenuItemById(menuId)
if (!menu) return false
return true
}, menuId)
expect(found).toBe(true)
}
export async function openSettingsExpectText(page: Page, text: string) {
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the user tab
const actualText = settings.getByText(text)
await expect(actualText).toBeVisible()
}
export async function openSettingsExpectLocator(page: Page, selector: string) {
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the keybindings tab
const settingsLocator = settings.locator(selector)
await expect(settingsLocator).toBeVisible()
}
/**
* A developer helper function to make playwright send all the console logs to stdout
* Call this within your E2E test and pass in the page or the tronApp to get as many
* logs piped to stdout for debugging
*/
export async function enableConsoleLogEverything({
page,
tronApp,
}: { page?: Page; tronApp?: ElectronZoo }) {
page?.on('console', (msg) => {
console.log(`[Page-log]: ${msg.text()}`)
})
tronApp?.electron.on('window', async (electronPage) => {
electronPage.on('console', (msg) => {
console.log(`[Renderer] ${msg.type()}: ${msg.text()}`)
})
})
tronApp?.electron.on('console', (msg) => {
console.log(`[Main] ${msg.type()}: ${msg.text()}`)
})
}

View File

@ -33,8 +33,8 @@ test.describe('Testing loading external models', () => {
// Locators and constants // Locators and constants
const newSample = { const newSample = {
file: 'pillow-block-bearing' + FILE_EXT, file: 'parametric-bearing-pillow-block' + FILE_EXT,
title: 'Pillow Block Bearing', title: 'Parametric Bearing Pillow Block',
} }
const commandBarButton = page.getByRole('button', { name: 'Commands' }) const commandBarButton = page.getByRole('button', { name: 'Commands' })
const samplesCommandOption = page.getByRole('option', { const samplesCommandOption = page.getByRole('option', {
@ -100,9 +100,9 @@ test.describe('Testing loading external models', () => {
// Locators and constants // Locators and constants
const sampleOne = { const sampleOne = {
file: 'ball-bearing' + FILE_EXT, file: 'parametric-bearing-pillow-block' + FILE_EXT,
title: 'Ball Bearing', title: 'Parametric Bearing Pillow Block',
file1: 'ball-bearing-1' + FILE_EXT, file1: 'parametric-bearing-pillow-block-1' + FILE_EXT,
} }
const projectCard = page.getByRole('link', { name: 'bracket' }) const projectCard = page.getByRole('link', { name: 'bracket' })
const overwriteWarning = page.getByText( const overwriteWarning = page.getByText(

View File

@ -4,10 +4,9 @@ import type { Page } from '@playwright/test'
import { createProject, getUtils } from '@e2e/playwright/test-utils' import { createProject, getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
test.describe('Text-to-CAD tests', () => { test.describe('Text-to-CAD tests', () => {
test('basic lego happy case', async ({ page, homePage, cmdBar }) => { test('basic lego happy case', async ({ page, homePage }) => {
const u = await getUtils(page) const u = await getUtils(page)
await test.step('Set up', async () => { await test.step('Set up', async () => {
@ -16,11 +15,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad() await u.waitForPageLoad()
}) })
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
page,
'a 2x4 lego',
cmdBar
)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -61,7 +56,6 @@ test.describe('Text-to-CAD tests', () => {
test('success model, then ignore success toast, user can create new prompt from command bar', async ({ test('success model, then ignore success toast, user can create new prompt from command bar', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -70,11 +64,7 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene() await homePage.goToModelingScene()
await u.waitForPageLoad() await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x6 lego')
page,
'a 2x6 lego',
cmdBar
)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -92,11 +82,7 @@ test.describe('Text-to-CAD tests', () => {
await expect(successToastMessage).toBeVisible({ timeout: 15000 }) await expect(successToastMessage).toBeVisible({ timeout: 15000 })
// Can send a new prompt from the command bar. // Can send a new prompt from the command bar.
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
page,
'a 2x4 lego',
cmdBar
)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -114,7 +100,6 @@ test.describe('Text-to-CAD tests', () => {
test('you can reject text-to-cad output and it does nothing', async ({ test('you can reject text-to-cad output and it does nothing', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -123,11 +108,7 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene() await homePage.goToModelingScene()
await u.waitForPageLoad() await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
page,
'a 2x4 lego',
cmdBar
)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -160,7 +141,6 @@ test.describe('Text-to-CAD tests', () => {
test('sending a bad prompt fails, can dismiss', async ({ test('sending a bad prompt fails, can dismiss', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -170,11 +150,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad() await u.waitForPageLoad()
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF` const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, randomPrompt)
page,
randomPrompt,
cmdBar
)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -212,7 +188,6 @@ test.describe('Text-to-CAD tests', () => {
test('sending a bad prompt fails, can start over from toast', async ({ test('sending a bad prompt fails, can start over from toast', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -222,7 +197,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad() await u.waitForPageLoad()
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss' const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar) await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -281,7 +256,6 @@ test.describe('Text-to-CAD tests', () => {
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({ test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -291,7 +265,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad() await u.waitForPageLoad()
const badPrompt = 'akjsndladflajbhflauweyf15;' const badPrompt = 'akjsndladflajbhflauweyf15;'
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar) await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -318,11 +292,7 @@ test.describe('Text-to-CAD tests', () => {
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
// They should be able to try again from the command bar. // They should be able to try again from the command bar.
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
page,
'a 2x4 lego',
cmdBar
)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -340,40 +310,17 @@ test.describe('Text-to-CAD tests', () => {
test('ensure you can shift+enter in the prompt box', async ({ test('ensure you can shift+enter in the prompt box', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
const projectName = await homePage.goToModelingScene() await homePage.goToModelingScene()
await u.waitForPageLoad() await u.waitForPageLoad()
const promptWithNewline = `a 2x4\nlego` const promptWithNewline = `a 2x4\nlego`
await test.step('Get to the prompt step to test', async () => { await page.getByTestId('text-to-cad').click()
await cmdBar.openCmdBar()
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
await cmdBar.currentArgumentInput.fill('existing')
await cmdBar.progressCmdBar()
await cmdBar.currentArgumentInput.fill(projectName)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Text-to-CAD Create',
stage: 'arguments',
currentArgKey: 'prompt',
currentArgValue: '',
highlightedHeaderArg: 'prompt',
headerArguments: {
Method: 'Existing project',
ProjectName: projectName,
Prompt: '',
},
})
})
// Type the prompt. // Type the prompt.
await page.keyboard.type('a 2x4') await page.keyboard.type('a 2x4')
@ -407,7 +354,6 @@ test.describe('Text-to-CAD tests', () => {
test('can do many at once and get many prompts back, and interact with many', async ({ test('can do many at once and get many prompts back, and interact with many', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
// Let this test run longer since we've seen it timeout. // Let this test run longer since we've seen it timeout.
test.setTimeout(180_000) test.setTimeout(180_000)
@ -419,23 +365,11 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene() await homePage.goToModelingScene()
await u.waitForPageLoad() await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x8 lego')
page,
'a 2x8 lego',
cmdBar
)
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x10 lego')
page,
'a 2x10 lego',
cmdBar
)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -506,7 +440,6 @@ test.describe('Text-to-CAD tests', () => {
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({ test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -515,16 +448,11 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene() await homePage.goToModelingScene()
await u.waitForPageLoad() await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(
page, page,
'alkjsdnlajshdbfjlhsbdf a;askjdnf', 'alkjsdnlajshdbfjlhsbdf a;askjdnf'
cmdBar
) )
// Find the toast. // Find the toast.
@ -598,9 +526,7 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
const cmdSearchBar = page.getByPlaceholder('Search commands') const cmdSearchBar = page.getByPlaceholder('Search commands')
await expect(cmdSearchBar).toBeVisible() await expect(cmdSearchBar).toBeVisible()
const textToCadCommand = page.getByRole('option', { const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API')
name: 'Text-to-CAD Create',
})
await expect(textToCadCommand.first()).toBeVisible() await expect(textToCadCommand.first()).toBeVisible()
// Click the Text-to-CAD command // Click the Text-to-CAD command
await textToCadCommand.first().scrollIntoViewIfNeeded() await textToCadCommand.first().scrollIntoViewIfNeeded()
@ -618,67 +544,29 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
}) })
} }
async function sendPromptFromCommandBarAndSetExistingProject( async function sendPromptFromCommandBarTriggeredByButton(
page: Page, page: Page,
promptStr: string, promptStr: string
cmdBar: CmdBarFixture,
projectName = 'testDefault'
) { ) {
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
await test.step(`Send prompt from command bar: ${promptStr}`, async () => { await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
await cmdBar.openCmdBar() await page.getByTestId('text-to-cad').click()
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
await cmdBar.expectState({ // Enter the prompt.
commandName: 'Text-to-CAD Create', const prompt = page.getByRole('textbox', { name: 'Prompt' })
stage: 'arguments', await expect(prompt.first()).toBeVisible()
currentArgKey: 'method',
currentArgValue: '',
highlightedHeaderArg: 'method',
headerArguments: {
Method: '',
Prompt: '',
},
})
await cmdBar.currentArgumentInput.fill('existing')
await cmdBar.progressCmdBar()
await cmdBar.expectState({ // Type the prompt.
commandName: 'Text-to-CAD Create', await page.keyboard.type(promptStr)
stage: 'arguments', await page.waitForTimeout(200)
currentArgKey: 'projectName', await page.keyboard.press('Enter')
currentArgValue: '',
highlightedHeaderArg: 'projectName',
headerArguments: {
Method: 'Existing project',
ProjectName: '',
Prompt: '',
},
})
await cmdBar.currentArgumentInput.fill(projectName)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Text-to-CAD Create',
stage: 'arguments',
currentArgKey: 'prompt',
currentArgValue: '',
highlightedHeaderArg: 'prompt',
headerArguments: {
Method: 'Existing project',
ProjectName: projectName,
Prompt: '',
},
})
await cmdBar.currentArgumentInput.fill(promptStr)
await cmdBar.progressCmdBar()
}) })
} }
test( test(
'Text-to-CAD functionality', 'Text-to-CAD functionality',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page, cmdBar }, testInfo) => { async ({ context, page }, testInfo) => {
const projectName = 'project-000' const projectName = 'project-000'
const prompt = 'lego 2x4' const prompt = 'lego 2x4'
const textToCadFileName = 'lego-2x4.kcl' const textToCadFileName = 'lego-2x4.kcl'
@ -715,12 +603,7 @@ test(
await openKclCodePanel() await openKclCodePanel()
await test.step(`Test file creation`, async () => { await test.step(`Test file creation`, async () => {
await sendPromptFromCommandBarAndSetExistingProject( await sendPromptFromCommandBarTriggeredByButton(page, prompt)
page,
prompt,
cmdBar,
projectName
)
// File is considered created if it shows up in the Project Files pane // File is considered created if it shows up in the Project Files pane
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 }) await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
expect(fileExists()).toBeTruthy() expect(fileExists()).toBeTruthy()
@ -890,12 +773,12 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
) )
await expect(page.getByTestId('app-header-file-name')).toBeVisible() await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText( await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl' '2x2x2-cube.kcl'
) )
await u.openFilePanel() await u.openFilePanel()
await expect( await expect(
page.getByTestId('file-tree-item').getByText('main.kcl') page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).toBeVisible() ).toBeVisible()
} }
) )
@ -1030,7 +913,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
) )
await expect(page.getByTestId('app-header-file-name')).toBeVisible() await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText( await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl' '2x2x2-cube.kcl'
) )
await page.getByRole('button', { name: 'Reject' }).click() await page.getByRole('button', { name: 'Reject' }).click()
@ -1078,7 +961,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
) )
await expect(page.getByTestId('app-header-file-name')).toBeVisible() await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText( await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl' '2x2x2-cube.kcl'
) )
} }
) )
@ -1330,14 +1213,18 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
) )
await expect(page.getByTestId('app-header-file-name')).toBeVisible() await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText( await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl' '2x2x2-cube.kcl'
) )
// Check file is created // Check file is created
await u.openFilePanel() await u.openFilePanel()
await expect( await expect(
page.getByTestId('file-tree-item').getByText('main.kcl') page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).toBeVisible() ).toBeVisible()
await expect(
page.getByTestId('file-tree-item').getByText('main.kcl')
).not.toBeVisible()
} }
) )

View File

@ -573,6 +573,7 @@ profile001 = startProfile(sketch002, at = [-12.34, 12.34])
await expect(page.getByTestId('command-bar')).toBeVisible() await expect(page.getByTestId('command-bar')).toBeVisible()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await expect(page.getByText('Confirm Extrude')).toBeVisible() await expect(page.getByText('Confirm Extrude')).toBeVisible()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()

View File

@ -74,7 +74,7 @@
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH = ELECTRON_OVERRIDE_DIST_PATH =
if pkgs.stdenv.isDarwin if pkgs.stdenv.isDarwin
then "${pkgs.electron}/Applications/Electron.app/Contents/MacOS/" then "${pkgs.electron}/Applications"
else "${pkgs.electron}/bin"; else "${pkgs.electron}/bin";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true; PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome"; PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";

View File

@ -94,6 +94,7 @@
"build:wasm:dev": "./scripts/build-wasm-dev.sh", "build:wasm:dev": "./scripts/build-wasm-dev.sh",
"build:wasm:dev:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/build-wasm-dev.ps1", "build:wasm:dev:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/build-wasm-dev.ps1",
"pretest": "npm run remove-importmeta", "pretest": "npm run remove-importmeta",
"test:rust": "(cd rust && just test && just lint)",
"simpleserver": "npm run pretest && http-server ./public --cors -p 3000", "simpleserver": "npm run pretest && http-server ./public --cors -p 3000",
"simpleserver:ci": "npm run pretest && http-server ./public --cors -p 3000 &", "simpleserver:ci": "npm run pretest && http-server ./public --cors -p 3000 &",
"simpleserver:bg": "npm run pretest && http-server ./public --cors -p 3000 &", "simpleserver:bg": "npm run pretest && http-server ./public --cors -p 3000 &",
@ -129,14 +130,15 @@
"tronb:package:prod": "npm run tronb:vite:prod && electron-builder --config electron-builder.yml --publish always", "tronb:package:prod": "npm run tronb:vite:prod && electron-builder --config electron-builder.yml --publish always",
"test-setup": "npm install && npm run build:wasm", "test-setup": "npm install && npm run build:wasm",
"test": "vitest --mode development", "test": "vitest --mode development",
"test:rust": "(cd rust && just test && just lint)",
"test:snapshots": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --grep=@snapshot --trace=on --shard=1/1", "test:snapshots": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --grep=@snapshot --trace=on --shard=1/1",
"test:unit": "vitest run --mode development --exclude **/jest-component-unit-tests/*", "test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts --exclude **/jest-component-unit-tests/*",
"test:unit:components": "jest -c jest-component-unit-tests/jest.config.ts --rootDir jest-component-unit-tests/", "test:unit:components": "jest -c jest-component-unit-tests/jest.config.ts --rootDir jest-component-unit-tests/",
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot", "test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:local": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"", "test:playwright:electron:local": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:playwright:electron:local-engine": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine' --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"", "test:playwright:electron:local-engine": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine' --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:unit:local": "npm run simpleserver:bg && npm run test:unit; kill-port 3000" "test:unit:local": "npm run simpleserver:bg && npm run test:unit; kill-port 3000",
"test:unit:kcl-samples:local": "npm run simpleserver:bg && npm run test:unit:kcl-samples; kill-port 3000"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [

View File

@ -31,36 +31,20 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![ball-bearing](screenshots/ball-bearing.png)](ball-bearing/main.kcl) [![ball-bearing](screenshots/ball-bearing.png)](ball-bearing/main.kcl)
#### [bench](bench/main.kcl) ([screenshot](screenshots/bench.png)) #### [bench](bench/main.kcl) ([screenshot](screenshots/bench.png))
[![bench](screenshots/bench.png)](bench/main.kcl) [![bench](screenshots/bench.png)](bench/main.kcl)
#### [bone-plate](bone-plate/main.kcl) ([screenshot](screenshots/bone-plate.png))
[![bone-plate](screenshots/bone-plate.png)](bone-plate/main.kcl)
#### [bottle](bottle/main.kcl) ([screenshot](screenshots/bottle.png)) #### [bottle](bottle/main.kcl) ([screenshot](screenshots/bottle.png))
[![bottle](screenshots/bottle.png)](bottle/main.kcl) [![bottle](screenshots/bottle.png)](bottle/main.kcl)
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png)) #### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
[![bracket](screenshots/bracket.png)](bracket/main.kcl) [![bracket](screenshots/bracket.png)](bracket/main.kcl)
#### [brake-rotor](brake-rotor/main.kcl) ([screenshot](screenshots/brake-rotor.png))
[![brake-rotor](screenshots/brake-rotor.png)](brake-rotor/main.kcl)
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png)) #### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png))
[![car-wheel-assembly](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl) [![car-wheel-assembly](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl)
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))
[![cold-plate](screenshots/cold-plate.png)](cold-plate/main.kcl)
#### [color-cube](color-cube/main.kcl) ([screenshot](screenshots/color-cube.png)) #### [color-cube](color-cube/main.kcl) ([screenshot](screenshots/color-cube.png))
[![color-cube](screenshots/color-cube.png)](color-cube/main.kcl) [![color-cube](screenshots/color-cube.png)](color-cube/main.kcl)
#### [counterdrilled-weldment](counterdrilled-weldment/main.kcl) ([screenshot](screenshots/counterdrilled-weldment.png))
[![counterdrilled-weldment](screenshots/counterdrilled-weldment.png)](counterdrilled-weldment/main.kcl)
#### [countersunk-plate](countersunk-plate/main.kcl) ([screenshot](screenshots/countersunk-plate.png))
[![countersunk-plate](screenshots/countersunk-plate.png)](countersunk-plate/main.kcl)
#### [cpu-cooler](cpu-cooler/main.kcl) ([screenshot](screenshots/cpu-cooler.png))
[![cpu-cooler](screenshots/cpu-cooler.png)](cpu-cooler/main.kcl)
#### [curtain-wall-anchor-plate](curtain-wall-anchor-plate/main.kcl) ([screenshot](screenshots/curtain-wall-anchor-plate.png))
[![curtain-wall-anchor-plate](screenshots/curtain-wall-anchor-plate.png)](curtain-wall-anchor-plate/main.kcl)
#### [cycloidal-gear](cycloidal-gear/main.kcl) ([screenshot](screenshots/cycloidal-gear.png)) #### [cycloidal-gear](cycloidal-gear/main.kcl) ([screenshot](screenshots/cycloidal-gear.png))
[![cycloidal-gear](screenshots/cycloidal-gear.png)](cycloidal-gear/main.kcl) [![cycloidal-gear](screenshots/cycloidal-gear.png)](cycloidal-gear/main.kcl)
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png)) #### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
[![dodecahedron](screenshots/dodecahedron.png)](dodecahedron/main.kcl) [![dodecahedron](screenshots/dodecahedron.png)](dodecahedron/main.kcl)
#### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png)) #### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png))
[![enclosure](screenshots/enclosure.png)](enclosure/main.kcl) [![enclosure](screenshots/enclosure.png)](enclosure/main.kcl)
#### [engine-valve](engine-valve/main.kcl) ([screenshot](screenshots/engine-valve.png))
[![engine-valve](screenshots/engine-valve.png)](engine-valve/main.kcl)
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([screenshot](screenshots/exhaust-manifold.png)) #### [exhaust-manifold](exhaust-manifold/main.kcl) ([screenshot](screenshots/exhaust-manifold.png))
[![exhaust-manifold](screenshots/exhaust-manifold.png)](exhaust-manifold/main.kcl) [![exhaust-manifold](screenshots/exhaust-manifold.png)](exhaust-manifold/main.kcl)
#### [flange](flange/main.kcl) ([screenshot](screenshots/flange.png)) #### [flange](flange/main.kcl) ([screenshot](screenshots/flange.png))
@ -71,6 +55,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![food-service-spatula](screenshots/food-service-spatula.png)](food-service-spatula/main.kcl) [![food-service-spatula](screenshots/food-service-spatula.png)](food-service-spatula/main.kcl)
#### [french-press](french-press/main.kcl) ([screenshot](screenshots/french-press.png)) #### [french-press](french-press/main.kcl) ([screenshot](screenshots/french-press.png))
[![french-press](screenshots/french-press.png)](french-press/main.kcl) [![french-press](screenshots/french-press.png)](french-press/main.kcl)
#### [gear](gear/main.kcl) ([screenshot](screenshots/gear.png))
[![gear](screenshots/gear.png)](gear/main.kcl)
#### [gear-rack](gear-rack/main.kcl) ([screenshot](screenshots/gear-rack.png)) #### [gear-rack](gear-rack/main.kcl) ([screenshot](screenshots/gear-rack.png))
[![gear-rack](screenshots/gear-rack.png)](gear-rack/main.kcl) [![gear-rack](screenshots/gear-rack.png)](gear-rack/main.kcl)
#### [gridfinity-baseplate](gridfinity-baseplate/main.kcl) ([screenshot](screenshots/gridfinity-baseplate.png)) #### [gridfinity-baseplate](gridfinity-baseplate/main.kcl) ([screenshot](screenshots/gridfinity-baseplate.png))
@ -81,18 +67,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![gridfinity-bins](screenshots/gridfinity-bins.png)](gridfinity-bins/main.kcl) [![gridfinity-bins](screenshots/gridfinity-bins.png)](gridfinity-bins/main.kcl)
#### [gridfinity-bins-stacking-lip](gridfinity-bins-stacking-lip/main.kcl) ([screenshot](screenshots/gridfinity-bins-stacking-lip.png)) #### [gridfinity-bins-stacking-lip](gridfinity-bins-stacking-lip/main.kcl) ([screenshot](screenshots/gridfinity-bins-stacking-lip.png))
[![gridfinity-bins-stacking-lip](screenshots/gridfinity-bins-stacking-lip.png)](gridfinity-bins-stacking-lip/main.kcl) [![gridfinity-bins-stacking-lip](screenshots/gridfinity-bins-stacking-lip.png)](gridfinity-bins-stacking-lip/main.kcl)
#### [hammer](hammer/main.kcl) ([screenshot](screenshots/hammer.png))
[![hammer](screenshots/hammer.png)](hammer/main.kcl)
#### [helical-gear](helical-gear/main.kcl) ([screenshot](screenshots/helical-gear.png))
[![helical-gear](screenshots/helical-gear.png)](helical-gear/main.kcl)
#### [helical-planetary-gearset](helical-planetary-gearset/main.kcl) ([screenshot](screenshots/helical-planetary-gearset.png))
[![helical-planetary-gearset](screenshots/helical-planetary-gearset.png)](helical-planetary-gearset/main.kcl)
#### [helium-tank](helium-tank/main.kcl) ([screenshot](screenshots/helium-tank.png))
[![helium-tank](screenshots/helium-tank.png)](helium-tank/main.kcl)
#### [herringbone-gear](herringbone-gear/main.kcl) ([screenshot](screenshots/herringbone-gear.png))
[![herringbone-gear](screenshots/herringbone-gear.png)](herringbone-gear/main.kcl)
#### [herringbone-planetary-gearset](herringbone-planetary-gearset/main.kcl) ([screenshot](screenshots/herringbone-planetary-gearset.png))
[![herringbone-planetary-gearset](screenshots/herringbone-planetary-gearset.png)](herringbone-planetary-gearset/main.kcl)
#### [hex-nut](hex-nut/main.kcl) ([screenshot](screenshots/hex-nut.png)) #### [hex-nut](hex-nut/main.kcl) ([screenshot](screenshots/hex-nut.png))
[![hex-nut](screenshots/hex-nut.png)](hex-nut/main.kcl) [![hex-nut](screenshots/hex-nut.png)](hex-nut/main.kcl)
#### [i-beam](i-beam/main.kcl) ([screenshot](screenshots/i-beam.png)) #### [i-beam](i-beam/main.kcl) ([screenshot](screenshots/i-beam.png))
@ -109,10 +83,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![mounting-plate](screenshots/mounting-plate.png)](mounting-plate/main.kcl) [![mounting-plate](screenshots/mounting-plate.png)](mounting-plate/main.kcl)
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png)) #### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
[![multi-axis-robot](screenshots/multi-axis-robot.png)](multi-axis-robot/main.kcl) [![multi-axis-robot](screenshots/multi-axis-robot.png)](multi-axis-robot/main.kcl)
#### [pdu-faceplate](pdu-faceplate/main.kcl) ([screenshot](screenshots/pdu-faceplate.png)) #### [parametric-bearing-pillow-block](parametric-bearing-pillow-block/main.kcl) ([screenshot](screenshots/parametric-bearing-pillow-block.png))
[![pdu-faceplate](screenshots/pdu-faceplate.png)](pdu-faceplate/main.kcl) [![parametric-bearing-pillow-block](screenshots/parametric-bearing-pillow-block.png)](parametric-bearing-pillow-block/main.kcl)
#### [pillow-block-bearing](pillow-block-bearing/main.kcl) ([screenshot](screenshots/pillow-block-bearing.png))
[![pillow-block-bearing](screenshots/pillow-block-bearing.png)](pillow-block-bearing/main.kcl)
#### [pipe](pipe/main.kcl) ([screenshot](screenshots/pipe.png)) #### [pipe](pipe/main.kcl) ([screenshot](screenshots/pipe.png))
[![pipe](screenshots/pipe.png)](pipe/main.kcl) [![pipe](screenshots/pipe.png)](pipe/main.kcl)
#### [pipe-flange-assembly](pipe-flange-assembly/main.kcl) ([screenshot](screenshots/pipe-flange-assembly.png)) #### [pipe-flange-assembly](pipe-flange-assembly/main.kcl) ([screenshot](screenshots/pipe-flange-assembly.png))
@ -121,38 +93,18 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![pipe-with-bend](screenshots/pipe-with-bend.png)](pipe-with-bend/main.kcl) [![pipe-with-bend](screenshots/pipe-with-bend.png)](pipe-with-bend/main.kcl)
#### [poopy-shoe](poopy-shoe/main.kcl) ([screenshot](screenshots/poopy-shoe.png)) #### [poopy-shoe](poopy-shoe/main.kcl) ([screenshot](screenshots/poopy-shoe.png))
[![poopy-shoe](screenshots/poopy-shoe.png)](poopy-shoe/main.kcl) [![poopy-shoe](screenshots/poopy-shoe.png)](poopy-shoe/main.kcl)
#### [prosthetic-hip](prosthetic-hip/main.kcl) ([screenshot](screenshots/prosthetic-hip.png))
[![prosthetic-hip](screenshots/prosthetic-hip.png)](prosthetic-hip/main.kcl)
#### [router-template-cross-bar](router-template-cross-bar/main.kcl) ([screenshot](screenshots/router-template-cross-bar.png)) #### [router-template-cross-bar](router-template-cross-bar/main.kcl) ([screenshot](screenshots/router-template-cross-bar.png))
[![router-template-cross-bar](screenshots/router-template-cross-bar.png)](router-template-cross-bar/main.kcl) [![router-template-cross-bar](screenshots/router-template-cross-bar.png)](router-template-cross-bar/main.kcl)
#### [router-template-slate](router-template-slate/main.kcl) ([screenshot](screenshots/router-template-slate.png)) #### [router-template-slate](router-template-slate/main.kcl) ([screenshot](screenshots/router-template-slate.png))
[![router-template-slate](screenshots/router-template-slate.png)](router-template-slate/main.kcl) [![router-template-slate](screenshots/router-template-slate.png)](router-template-slate/main.kcl)
#### [sash-window](sash-window/main.kcl) ([screenshot](screenshots/sash-window.png))
[![sash-window](screenshots/sash-window.png)](sash-window/main.kcl)
#### [sheet-metal-bracket](sheet-metal-bracket/main.kcl) ([screenshot](screenshots/sheet-metal-bracket.png)) #### [sheet-metal-bracket](sheet-metal-bracket/main.kcl) ([screenshot](screenshots/sheet-metal-bracket.png))
[![sheet-metal-bracket](screenshots/sheet-metal-bracket.png)](sheet-metal-bracket/main.kcl) [![sheet-metal-bracket](screenshots/sheet-metal-bracket.png)](sheet-metal-bracket/main.kcl)
#### [shepherds-hook-bolt](shepherds-hook-bolt/main.kcl) ([screenshot](screenshots/shepherds-hook-bolt.png))
[![shepherds-hook-bolt](screenshots/shepherds-hook-bolt.png)](shepherds-hook-bolt/main.kcl)
#### [socket-head-cap-screw](socket-head-cap-screw/main.kcl) ([screenshot](screenshots/socket-head-cap-screw.png)) #### [socket-head-cap-screw](socket-head-cap-screw/main.kcl) ([screenshot](screenshots/socket-head-cap-screw.png))
[![socket-head-cap-screw](screenshots/socket-head-cap-screw.png)](socket-head-cap-screw/main.kcl) [![socket-head-cap-screw](screenshots/socket-head-cap-screw.png)](socket-head-cap-screw/main.kcl)
#### [spinning-highrise-tower](spinning-highrise-tower/main.kcl) ([screenshot](screenshots/spinning-highrise-tower.png))
[![spinning-highrise-tower](screenshots/spinning-highrise-tower.png)](spinning-highrise-tower/main.kcl)
#### [spur-gear](spur-gear/main.kcl) ([screenshot](screenshots/spur-gear.png))
[![spur-gear](screenshots/spur-gear.png)](spur-gear/main.kcl)
#### [spur-reduction-gearset](spur-reduction-gearset/main.kcl) ([screenshot](screenshots/spur-reduction-gearset.png))
[![spur-reduction-gearset](screenshots/spur-reduction-gearset.png)](spur-reduction-gearset/main.kcl)
#### [surgical-drill-guide](surgical-drill-guide/main.kcl) ([screenshot](screenshots/surgical-drill-guide.png))
[![surgical-drill-guide](screenshots/surgical-drill-guide.png)](surgical-drill-guide/main.kcl)
#### [thermal-block-insert](thermal-block-insert/main.kcl) ([screenshot](screenshots/thermal-block-insert.png))
[![thermal-block-insert](screenshots/thermal-block-insert.png)](thermal-block-insert/main.kcl)
#### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png))
[![tooling-nest-block](screenshots/tooling-nest-block.png)](tooling-nest-block/main.kcl)
#### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png)) #### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png))
[![utility-sink](screenshots/utility-sink.png)](utility-sink/main.kcl) [![utility-sink](screenshots/utility-sink.png)](utility-sink/main.kcl)
#### [walkie-talkie](walkie-talkie/main.kcl) ([screenshot](screenshots/walkie-talkie.png)) #### [walkie-talkie](walkie-talkie/main.kcl) ([screenshot](screenshots/walkie-talkie.png))
[![walkie-talkie](screenshots/walkie-talkie.png)](walkie-talkie/main.kcl) [![walkie-talkie](screenshots/walkie-talkie.png)](walkie-talkie/main.kcl)
#### [washer](washer/main.kcl) ([screenshot](screenshots/washer.png)) #### [washer](washer/main.kcl) ([screenshot](screenshots/washer.png))
[![washer](screenshots/washer.png)](washer/main.kcl) [![washer](screenshots/washer.png)](washer/main.kcl)
#### [wing-spar](wing-spar/main.kcl) ([screenshot](screenshots/wing-spar.png))
[![wing-spar](screenshots/wing-spar.png)](wing-spar/main.kcl)

View File

@ -57,9 +57,9 @@ fn connectorSketch(@plane, start) {
export fn connector(@plane, length) { export fn connector(@plane, length) {
connectorSketch(plane, start = [-12, 8]) connectorSketch(plane, start = [-12, 8])
|> extrude(length) |> extrude(length = length)
connectorSketch(plane, start = [16, 8]) connectorSketch(plane, start = [16, 8])
|> extrude(length) |> extrude(length = length)
return 0 return 0
} }
@ -79,7 +79,7 @@ fn seatSlatSketch(@plane) {
export fn seatSlats(@plane, length) { export fn seatSlats(@plane, length) {
seatSlatSketch(plane) seatSlatSketch(plane)
|> extrude(length) |> extrude(length = length)
return 0 return 0
} }
@ -99,7 +99,7 @@ fn backSlatsSketch(@plane) {
export fn backSlats(@plane, length) { export fn backSlats(@plane, length) {
b = backSlatsSketch(plane) b = backSlatsSketch(plane)
|> extrude(length) |> extrude(length = length)
return b return b
} }

View File

@ -1,55 +0,0 @@
// Bone Plate
// A bone plate is a medical device used in orthopedics to stabilize and fix bone fractures during the healing process. They are typically made of stainless steel or titanium and are secured to the bone with screws. Bone plates come in various types, including locking, compression, and bridge plates, each with specific applications
// Set units
@settings(defaultLengthUnit = mm)
// Define parameters
boltSize = 4.5
// Revolve the profile of a compression plate designed to fit a bone
plateRevolve = startSketchOn(YZ)
|> startProfile(at = [22.9, 0])
|> arc(angleStart = 180, angleEnd = 176, radius = 120)
|> arc(angleStart = -60, angleEnd = 54, radius = 5)
|> arc(angleStart = 180, angleEnd = 176, radius = 120)
|> arc(angleStart = -60, angleEnd = 54, radius = 5)
|> arc(angleStart = 180, angleEnd = 176, radius = 120)
|> arc(angleStart = -60, angleEnd = 54, radius = 5)
|> arc(angleStart = 180, angleEnd = 174, radius = 170)
|> tangentialArc(endAbsolute = [41.8, 91.88])
|> tangentialArc(endAbsolute = [56.92, 117.08], tag = $seg01)
|> angledLine(angle = tangentToEnd(seg01), length = 23.16)
|> tangentialArc(endAbsolute = [60.93, 140.44], tag = $seg02)
|> angledLine(angle = tangentToEnd(seg02), length = 25.65)
|> tangentialArc(endAbsolute = [48.35, 85.53])
|> tangentialArc(endAbsolute = [35.2, 67.73], tag = $seg03)
|> angledLine(angle = tangentToEnd(seg03), length = 49.06)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> revolve(axis = Y, angle = 65, symmetric = true)
// Create a hole sketch with the size and location of each bolt hole
holeSketch = startSketchOn(XZ)
hole01 = circle(holeSketch, center = [0, 12.25], radius = boltSize / 2)
|> extrude(length = -100)
hole02 = circle(holeSketch, center = [0, 29.5], radius = boltSize / 2)
|> extrude(length = -100)
hole03 = circle(holeSketch, center = [0, 46.25], radius = boltSize / 2)
|> extrude(length = -100)
hole04 = circle(holeSketch, center = [0, 77], radius = boltSize / 2)
|> extrude(length = -100)
hole05 = circle(holeSketch, center = [0, 100], radius = boltSize / 2)
|> extrude(length = -100)
hole06 = circle(holeSketch, center = [0, 130], radius = boltSize / 2)
|> extrude(length = -100)
hole07 = circle(holeSketch, center = [-20, 130], radius = boltSize / 2)
|> extrude(length = -100)
hole08 = circle(holeSketch, center = [20, 130], radius = boltSize / 2)
|> extrude(length = -100)
// Cut each guiding clearance hole from the bone plate
solid001 = subtract([plateRevolve], tools = union([hole01, hole02]))
solid002 = subtract([solid001], tools = union([hole03, hole04]))
solid003 = subtract([solid002], tools = union([hole05, hole06]))
solid004 = subtract([solid003], tools = union([hole07, hole08]))

View File

@ -1,179 +0,0 @@
// A 320mm vented brake disc (rotor), with straight vanes, 30mm thick. The disc bell should accommodate 5 M12 wheel studs on a 114.3mm pitch circle diameter.
@settings(defaultLengthUnit = mm)
// Define parameters.
dDisc = 320
dPitchCircle = 114.3
dBore = 64
nStuds = 5
dStudDrilling = 12.5 // M12
hFrictionSurface = 60
tDiscHalf = 10
// Vent parameters.
tVent = 10
wVent = 6
rVentFillet = 2
nVentBosses = 36
// Drilling parameters.
dDrillDia = 6
aBase = 90
aSweep = 30
nArcs = 12
// Bell parameters.
aDraftBell = 5
tBell = 5 // Wall thickness.
hBellAboveDiscFace = 40
hBellSubflush = 4
wUndercut = 8
fn drillHole(activeSketch, t) {
// Sketch a vent hole at line parameter value t on an arc drawn across the disc surface.
rInner = dDisc / 2 - hFrictionSurface
rOuter = dDisc / 2
aStart = aBase
aEnd = aBase - aSweep
// Linear interpolation of radius.
rCurrent = rInner + t * (rOuter - rInner)
// Linear interpolation of angle.
aCurrent = aStart + t * (aEnd - aStart)
// Calculate position.
xCenter = rCurrent * cos(aCurrent)
yCenter = rCurrent * sin(aCurrent)
// Draw.
drillCircle = circle(activeSketch, center = [xCenter, yCenter], radius = dDrillDia / 2)
return drillCircle
}
fn createDiscHalf(plane, dDiscParam, hFrictionSurfaceParam, tDiscHalfParam) {
// Create a disc half with a vent hole pattern.
sketchFace = startSketchOn(plane)
profileFace = circle(sketchFace, center = [0, 0], radius = dDiscParam / 2)
|> subtract2d(tool = circle(sketchFace, center = [0, 0], radius = dDiscParam / 2 - hFrictionSurfaceParam))
// Create three circles at t = 0, 0.5, and 1
hole1 = drillHole(activeSketch = sketchFace, t = 0.2)
hole2 = drillHole(activeSketch = sketchFace, t = 0.5)
hole3 = drillHole(activeSketch = sketchFace, t = 0.8)
// Pattern and cut.
holes = patternCircular2d(
[hole1, hole2, hole3],
instances = nArcs,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
profileDrilled = subtract2d(profileFace, tool = holes)
// Extrude.
discHalf = extrude(profileFace, length = tDiscHalfParam)
return discHalf
}
// ---------------------------------------------------------------------------------------------------------------------
// Create inboard half.
discInboard = createDiscHalf(
plane = XY,
dDiscParam = dDisc,
hFrictionSurfaceParam = hFrictionSurface,
tDiscHalfParam = tDiscHalf,
)
// Create vents.
planeVent = offsetPlane(XY, offset = tDiscHalf)
sketchVent = startSketchOn(planeVent)
profileVent = startProfile(sketchVent, at = [-wVent, dDisc / 2])
|> angledLine(angle = 0, length = wVent, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = hFrictionSurface, tag = $seg02)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg03)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|> close()
ventPad = extrude(profileVent, length = tVent)
|> fillet(
radius = rVentFillet,
tags = [
getCommonEdge(faces = [seg01, rectangleSegmentA001]),
getCommonEdge(faces = [seg02, rectangleSegmentA001]),
getCommonEdge(faces = [seg01, seg03]),
getCommonEdge(faces = [seg03, seg02])
],
)
ventSet = patternCircular3d(
ventPad,
instances = nVentBosses,
axis = [0, 0, 1],
center = [0, 0, tDiscHalf],
arcDegrees = 360,
rotateDuplicates = true,
)
// Create outboard half.
planeOutboard = offsetPlane(XY, offset = tDiscHalf + tVent)
discOutboard = createDiscHalf(
plane = planeOutboard,
dDiscParam = dDisc,
hFrictionSurfaceParam = hFrictionSurface,
tDiscHalfParam = tDiscHalf,
)
// Now create bell.
rCenter = dDisc / 2 - hFrictionSurface - wUndercut
rBore = dBore / 2
lDraftExterior = hBellAboveDiscFace / tan(90 - aDraftBell)
lDraftInterior = (hBellAboveDiscFace - tBell) / tan(90 - aDraftBell)
// Inner and outer radius of outboard face of disc bell.
rOuter = rCenter - lDraftExterior - rBore
rInner = rOuter + lDraftExterior - (tBell + lDraftInterior)
sketchDiscBell = startSketchOn(-YZ)
bodyDiscBell = startProfile(
sketchDiscBell,
at = [
-dDisc / 2 + hFrictionSurface,
tDiscHalf * 2 + tVent
],
)
|> arc(
%,
angleStart = -180,
angleEnd = 0,
radius = wUndercut / 2,
)
|> line(end = [lDraftExterior, hBellAboveDiscFace])
|> xLine(length = rOuter, tag = $seg04)
|> yLine(length = -tBell)
|> xLine(length = -rInner)
|> line(end = [-lDraftInterior, -hBellAboveDiscFace])
|> line(end = [0, -2]) // Wall thickness.
|> xLine(length = -1 * (tBell + wUndercut))
|> close(%)
|> revolve(axis = Y)
// Drill lug holes.
sketchLugs = startSketchOn(bodyDiscBell, face = seg04)
profileStud = circle(sketchLugs, center = [0, dPitchCircle / 2], radius = dStudDrilling / 2)
|> patternCircular2d(
%,
instances = nStuds,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
clearance = 2 // Some margin on negative extrude.
lugs = extrude(profileStud, length = -1 * (tBell + clearance))

View File

@ -1,67 +0,0 @@
// Cold Plate
// A cold plate is a thermal management device used to remove heat from a device or component, typically by transferring heat to a liquid coolant that flows through the plate. It's a conductive cooling solution, commonly made of materials like aluminum or copper, with internal channels or tubes for the coolant
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
tubeDiameter = 5 / 8
wallThickness = 0.080
bendRadius = 1
// Create the cold plate with indentions to secure each pass of the brazed copper tube
coldPlate = startSketchOn(YZ)
|> startProfile(at = [0, tubeDiameter * 2])
|> xLine(length = bendRadius - (tubeDiameter / 2))
|> yLine(length = -tubeDiameter)
|> tangentialArc(angle = 180, radius = tubeDiameter / 2)
|> yLine(length = tubeDiameter)
|> xLine(length = bendRadius * 2 - tubeDiameter, tag = $seg07)
|> yLine(length = -tubeDiameter, tag = $seg09)
|> tangentialArc(angle = 180, radius = tubeDiameter / 2)
|> yLine(length = tubeDiameter, tag = $seg08)
|> xLine(length = bendRadius - (tubeDiameter / 2))
|> angledLine(angle = -77, length = tubeDiameter / 3)
|> tangentialArc(angle = 77, radius = tubeDiameter, tag = $seg01)
|> angledLine(angle = tangentToEnd(seg01), length = 1)
|> yLine(endAbsolute = 0)
|> xLine(endAbsolute = 0)
|> mirror2d(axis = Y)
|> close()
|> extrude(length = 10, symmetric = true)
// Sketch the path for the copper tube to follow
copperTubePath = startSketchOn(offsetPlane(XY, offset = tubeDiameter))
|> startProfile(at = [-7.35, -bendRadius * 3])
|> xLine(length = 14.13, tag = $seg05)
|> tangentialArc(angle = 180, radius = bendRadius, tag = $seg02)
|> angledLine(angle = tangentToEnd(seg02), length = 13.02, tag = $seg06)
|> tangentialArc(angle = -180, radius = bendRadius, tag = $seg03)
|> angledLine(angle = tangentToEnd(seg03), length = segLen(seg06))
|> tangentialArc(angle = 180, radius = bendRadius, tag = $seg04)
|> angledLine(angle = tangentToEnd(seg04), length = segLen(seg05))
// Create the profile for the inner and outer diameter of the hollow copper tube
tubeWall = startSketchOn(offsetPlane(YZ, offset = -7.35))
|> circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2)
|> subtract2d(%, tool = circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2 - wallThickness))
|> sweep(path = copperTubePath)
|> appearance(color = "#b81b0a")
// Model a brazed cap to cover each tube. Constrain the caps using the walls of the plate
brazedCap = startSketchOn(YZ)
|> startProfile(at = segEnd(seg07))
|> arc(interiorAbsolute = [bendRadius * 3, tubeDiameter * 1.85], endAbsolute = segEnd(seg08))
|> yLine(endAbsolute = segStartY(seg08))
|> arc(
interiorAbsolute = [
bendRadius * 3,
segEndY(seg09) + tubeDiameter / 2
],
endAbsolute = segEnd(seg09),
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(length = 10, symmetric = true)
|> patternLinear3d(instances = 4, distance = bendRadius * 2, axis = [0, -1, 0])
|> appearance(color = "#6b261e")

View File

@ -1,120 +0,0 @@
// Counterdrilled Weldment
// A metal weldment consisting of a counterdrilled plate, a centrally mounted housing tube, and four structural support fins.
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
boltSpacingX = 5
boltSpacingY = 3
boltDiameter = 1 / 4
counterdrillDiameter = 7 / 16
counterdrillDepth = 3 / 16
tubeInnerDiameter = 1 + 1 / 4
tubeThickness = 0.115
tubeHeight = 2
stockThickness = .5
// Calculate the dimensions of the block using the specified bolt spacing. The size of the block can be defined by adding a multiple of the counterdrill diameter to the bolt spacing
blockLength = boltSpacingX + boltDiameter * 6
blockWidth = boltSpacingY + boltDiameter * 6
// Draw the base plate
plateSketch = startSketchOn(XY)
|> startProfile(at = [-blockLength / 2, -blockWidth / 2])
|> angledLine(angle = 0, length = blockLength, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = blockWidth, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
|> subtract2d(tool = circle(center = [0, 0], radius = tubeInnerDiameter / 2))
plateBody = extrude(plateSketch, length = stockThickness)
|> chamfer(
length = boltDiameter * 2,
tags = [
getNextAdjacentEdge(rectangleSegmentB001),
getNextAdjacentEdge(rectangleSegmentA001),
getNextAdjacentEdge(rectangleSegmentC001),
getNextAdjacentEdge(rectangleSegmentD001)
],
)
// Define hole positions
holePositions = [
[-boltSpacingX / 2, -boltSpacingY / 2],
[-boltSpacingX / 2, boltSpacingY / 2],
[boltSpacingX / 2, -boltSpacingY / 2],
[boltSpacingX / 2, boltSpacingY / 2]
]
// Function to create a counterdrilled hole
fn counterdrill(@holePosition) {
cbdrill = startSketchOn(plateBody, face = END)
|> circle(center = holePosition, radius = counterdrillDiameter / 2)
|> extrude(length = -counterdrillDepth)
cbBolt = startSketchOn(cbdrill, face = START)
|> circle(center = holePosition, radius = boltDiameter / 2, tag = $hole01)
|> extrude(length = -stockThickness + counterdrillDepth)
// Use a chamfer to create a 90-degree counterdrill edge
|> chamfer(length = (counterdrillDiameter - boltDiameter) / 2 * sqrt(2), tags = [hole01])
return { }
}
// Place a counterdrilled hole at each bolt hole position
map(holePositions, f = counterdrill)
// Drill a small pin hole in the side of the tube
pinhole = startSketchOn(YZ)
|> circle(center = [0, 2.2], radius = 0.125)
|> extrude(length = -10)
// Model the central tube and subtract the pin hole
centralTube = startSketchOn(offsetPlane(XY, offset = stockThickness))
|> circle(center = [0, 0], radius = tubeInnerDiameter / 2 + tubeThickness)
|> subtract2d(tool = circle(center = [0, 0], radius = tubeInnerDiameter / 2))
|> extrude(length = tubeHeight)
|> subtract(tools = [pinhole])
// Create a function to create a fin which spans from the central tube to the bolt hole
fn fin(@i) {
diagPlane = {
origin = [0.0, 0.0, 0.0],
xAxis = [
boltSpacingX / 2 * i,
boltSpacingY / 2,
0.0
],
yAxis = [0.0, 0.0, 1.0]
}
finSketch = startSketchOn(diagPlane)
|> startProfile(at = [
tubeInnerDiameter / 2 + tubeThickness,
stockThickness
])
|> xLine(endAbsolute = sqrt((boltSpacingX / 2) ^ 2 + (boltSpacingY / 2) ^ 2) - counterdrillDiameter)
|> yLine(length = 0.15)
|> line(endAbsolute = [
profileStartX(%) + 0.15,
stockThickness + tubeHeight * .8
])
|> xLine(length = -0.15)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(length = tubeThickness, symmetric = true)
// Use a circular pattern to create an identical fin on the opposite side
otherFin = patternCircular3d(
finSketch,
instances = 2,
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
return { }
}
// Place a pair of support fins along each diagonal axis of the bolt pattern
fin(1)
fin(-1)

View File

@ -1,50 +0,0 @@
// Plate with countersunk holes
// A small mounting plate with a countersunk hole at each end
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
boltSpacing = 5
boltDiameter = 1 / 4
centerHoleDiameter = 1 + 3 / 4
plateThickness = 0.375
// Check that the plate is thick enough to countersink a hole
// assertGreaterThan(plateThickness, boltDiameter, "This plate is not thick enough for the necessary countersink dimensions")
// A bit of math to calculate the tangent line between the two diameters
r1 = centerHoleDiameter / 2 * 1.5 + .35
r2 = boltDiameter * 2 + .25
d = boltSpacing / 2
tangentAngle = asin((r1 - r2) / d)
tangentLength = (r1 - r2) / tan(tangentAngle)
plateBody = startSketchOn(XY)
// Use polar coordinates to start the sketch at the tangent point of the larger radius
|> startProfile(at = polar(angle = 90 - tangentAngle, length = r1))
|> angledLine(angle = -tangentAngle, length = tangentLength)
|> tangentialArc(radius = r2, angle = (tangentAngle - 90) * 2)
|> angledLine(angle = tangentAngle, length = -tangentLength)
|> tangentialArc(radius = r1, angle = -tangentAngle * 2)
|> angledLine(angle = -tangentAngle, length = -tangentLength)
|> tangentialArc(radius = r2, angle = (tangentAngle - 90) * 2)
|> angledLine(angle = tangentAngle, length = tangentLength)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> subtract2d(tool = circle(center = [0, 0], radius = centerHoleDiameter / 2 * 1.5))
|> extrude(%, length = plateThickness)
// Function to create a countersunk hole
fn countersink(@holePosition) {
startSketchOn(plateBody, face = END)
|> circle(center = [holePosition, 0], radius = boltDiameter / 2, tag = $hole01)
|> extrude(length = -plateThickness)
// Use a chamfer to create a 90-degree countersink
|> chamfer(length = boltDiameter, tags = [hole01])
return { }
}
// Place a countersunk hole at each bolt hole position
countersink(-boltSpacing / 2)
countersink(boltSpacing / 2)

View File

@ -1,153 +0,0 @@
// Fan Housing
// The plastic housing that contains the fan and the motor
// Set units
@settings(defaultLengthUnit = mm)
// Import parameters
import * from "parameters.kcl"
// Model the housing which holds the motor, the fan, and the mounting provisions
// Bottom mounting face
bottomFaceSketch = startSketchOn(YZ)
|> startProfile(at = [-fanSize / 2, -fanSize / 2])
|> angledLine(angle = 0, length = fanSize, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = fanSize, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
|> subtract2d(tool = circle(center = [0, 0], radius = 4))
|> subtract2d(tool = circle(
center = [
mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
-mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
-mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> extrude(length = 4)
// Add large openings to the bottom face to allow airflow through the fan
airflowPattern = startSketchOn(bottomFaceSketch, face = END)
|> startProfile(at = [fanSize * 7 / 25, -fanSize * 9 / 25])
|> angledLine(angle = 140, length = fanSize * 12 / 25, tag = $seg01)
|> tangentialArc(radius = fanSize * 1 / 50, angle = 90)
|> angledLine(angle = -130, length = fanSize * 8 / 25)
|> tangentialArc(radius = fanSize * 1 / 50, angle = 90)
|> angledLine(angle = segAng(seg01) + 180, length = fanSize * 2 / 25)
|> tangentialArc(radius = fanSize * 8 / 25, angle = 40)
|> xLine(length = fanSize * 3 / 25)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternCircular2d(
instances = 4,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
|> extrude(length = -4)
// Create the middle segment of the fan housing body
housingMiddleLength = fanSize / 3
housingMiddleRadius = fanSize / 3 - 1
bodyMiddle = startSketchOn(bottomFaceSketch, face = END)
|> startProfile(at = [
housingMiddleLength / 2,
-housingMiddleLength / 2 - housingMiddleRadius
])
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> yLine(length = housingMiddleLength)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> xLine(length = -housingMiddleLength)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> yLine(length = -housingMiddleLength)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> extrude(length = fanHeight - 4 - 4)
// Cut a hole in the body to accommodate the fan
bodyFanHole = startSketchOn(bodyMiddle, face = END)
|> circle(center = [0, 0], radius = fanSize * 23 / 50)
|> extrude(length = -(fanHeight - 4 - 4))
// Top mounting face. Cut a hole in the face to accommodate the fan
topFaceSketch = startSketchOn(bodyMiddle, face = END)
topHoles = startProfile(topFaceSketch, at = [-fanSize / 2, -fanSize / 2])
|> angledLine(angle = 0, length = fanSize, tag = $rectangleSegmentA002)
|> angledLine(angle = segAng(rectangleSegmentA002) + 90, length = fanSize, tag = $rectangleSegmentB002)
|> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002), tag = $rectangleSegmentC002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD002)
|> close()
|> subtract2d(tool = circle(center = [0, 0], radius = fanSize * 23 / 50))
|> subtract2d(tool = circle(
center = [
mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
-mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
-mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> extrude(length = 4)
// Create a housing for the electric motor to sit
motorHousing = startSketchOn(bottomFaceSketch, face = END)
|> circle(center = [0, 0], radius = 11.2)
|> extrude(length = 16)
startSketchOn(motorHousing, face = END)
|> circle(center = [0, 0], radius = 10)
|> extrude(length = -16)
|> appearance(color = "#a55e2c")
|> fillet(
radius = abs(fanSize - mountingHoleSpacing) / 2,
tags = [
getNextAdjacentEdge(rectangleSegmentA001),
getNextAdjacentEdge(rectangleSegmentB001),
getNextAdjacentEdge(rectangleSegmentC001),
getNextAdjacentEdge(rectangleSegmentD001),
getNextAdjacentEdge(rectangleSegmentA002),
getNextAdjacentEdge(rectangleSegmentB002),
getNextAdjacentEdge(rectangleSegmentC002),
getNextAdjacentEdge(rectangleSegmentD002)
],
)

View File

@ -1,71 +0,0 @@
// Fan
// Spinning axial fan that moves airflow
// Set units
@settings(defaultLengthUnit = mm)
// Import parameters
import * from "parameters.kcl"
// Model the center of the fan
fanCenter = startSketchOn(YZ)
|> circle(center = [0, 0], radius = fanHeight / 2, tag = $centerBend)
|> extrude(%, length = fanHeight)
|> fillet(radius = 1.5, tags = [getOppositeEdge(centerBend)])
// Create a function for a lofted fan blade cross section that rotates about the center hub of the fan
fn fanBlade(offsetHeight, startAngle) {
fanBlade = startSketchOn(offsetPlane(YZ, offset = offsetHeight))
|> startProfile(at = [
15 * cos(startAngle),
15 * sin(startAngle)
])
|> arc(angleStart = startAngle, angleEnd = startAngle + 14, radius = 15)
|> arc(
endAbsolute = [
fanSize * 22 / 50 * cos(startAngle - 20),
fanSize * 22 / 50 * sin(startAngle - 20)
],
interiorAbsolute = [
fanSize * 11 / 50 * cos(startAngle + 3),
fanSize * 11 / 50 * sin(startAngle + 3)
],
)
|> arc(
endAbsolute = [
fanSize * 22 / 50 * cos(startAngle - 24),
fanSize * 22 / 50 * sin(startAngle - 24)
],
interiorAbsolute = [
fanSize * 22 / 50 * cos(startAngle - 22),
fanSize * 22 / 50 * sin(startAngle - 22)
],
)
|> arc(
endAbsolute = [profileStartX(%), profileStartY(%)],
interiorAbsolute = [
fanSize * 11 / 50 * cos(startAngle - 5),
fanSize * 11 / 50 * sin(startAngle - 5)
],
)
|> close()
return fanBlade
}
// Loft the fan blade cross sections into a single blade, then pattern them about the fan center
crossSections = [
fanBlade(offsetHeight = 4.5, startAngle = 50),
fanBlade(offsetHeight = (fanHeight - 2 - 4) / 2, startAngle = 30),
fanBlade(offsetHeight = fanHeight - 2, startAngle = 0)
]
bladeLoft = loft(crossSections)
|> patternCircular3d(
instances = 9,
axis = [1, 0, 0],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
[fanCenter, bladeLoft]
|> appearance(color = "#110803")

View File

@ -1,171 +0,0 @@
// Heat Sink
// Conductive metal device made from brazed tubes and fins
// Set units
@settings(defaultLengthUnit = mm)
// Import parameters
import * from "parameters.kcl"
// Draw the sweep path for the outermost tubes
endTubePath = startSketchOn(offsetPlane(YZ, offset = -20))
|> startProfile(at = [fanSize / 4, fanSize + 38])
|> yLine(endAbsolute = bendRadius + 10, tag = $seg01)
|> tangentialArc(radius = bendRadius, angle = -90)
|> xLine(endAbsolute = 0, tag = $seg02)
|> xLine(length = -segLen(seg02))
|> tangentialArc(radius = bendRadius, angle = -90)
|> yLine(length = segLen(seg01))
// Sweep and translate the outermost tube on each end
endTube = startSketchOn(offsetPlane(XY, offset = fanSize + 38))
|> circle(center = [-20, fanSize / 4], radius = 3)
|> subtract2d(tool = circle(center = [-20, fanSize / 4], radius = 2.5))
|> sweep(path = endTubePath)
|> patternCircular3d(
%,
instances = 2,
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = false,
)
// Draw the sweep path for the 4 interior tubes
centerTubePath = startSketchOn(offsetPlane(YZ, offset = -4))
|> startProfile(at = [fanSize / 2.67, fanSize + 38])
|> yLine(endAbsolute = bendRadius + 15 + 10)
|> tangentialArc(radius = bendRadius, angle = -45)
|> angledLine(angle = -135, lengthY = 15)
|> tangentialArc(radius = bendRadius, angle = -45)
|> xLine(endAbsolute = 0, tag = $seg03)
|> xLine(length = -segLen(seg03))
|> tangentialArc(radius = bendRadius, angle = -155)
|> tangentialArc(radius = bendRadius, angle = 65)
|> yLine(endAbsolute = fanSize + 38)
// Draw the profile and sweep the 4 interior tubes
centerTube = startSketchOn(offsetPlane(XY, offset = fanSize + 38))
|> circle(center = [-4, fanSize / 2.67], radius = 3)
|> subtract2d(tool = circle(center = [-4, fanSize / 2.67], radius = 2.5))
|> sweep(path = centerTubePath)
|> patternCircular3d(
%,
instances = 2,
axis = [0, 0, 1],
center = [-4 * 2, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
|> patternLinear3d(
%,
instances = 2,
distance = 4 * 4,
axis = [1, 0, 0],
)
// Draw a heat fin with built-in clips to secure the mounting wire. Pattern the fin upwards by the height of the fan
heatFins = startSketchOn(offsetPlane(XY, offset = 45))
|> startProfile(at = [0, -fanSize / 2])
|> xLine(length = 9)
|> angledLine(angle = -60, length = 2.5, tag = $seg04)
|> xLine(length = 0.75)
|> arc(interiorAbsolute = [lastSegX(%) + 1, lastSegY(%) + 1.2], endAbsolute = [lastSegX(%) + 2, lastSegY(%)])
|> xLine(length = 0.75)
|> angledLine(angle = 60, length = segLen(seg04))
|> xLine(endAbsolute = heatSinkDepth / 2 - 3)
|> tangentialArc(angle = 90, radius = 3)
|> yLine(endAbsolute = 0)
|> mirror2d(axis = X)
|> mirror2d(axis = Y)
|> close()
|> extrude(length = 1)
|> patternLinear3d(
%,
instances = 31,
distance = (fanSize - 10) / 30,
axis = [0, 0, 1],
)
// Create the mounting base for the CPU cooler. The base should consist of two pieces that secure around each of the tubes at the bottom
coolerBase = startSketchOn(-XZ)
baseLower = startProfile(coolerBase, at = [0, 10])
|> xLine(length = -0.9)
|> arc(angleStart = 0, angleEnd = -180, radius = 3.1)
|> xLine(length = -1.8)
|> arc(angleStart = 0, angleEnd = -180, radius = 3)
|> xLine(length = -1.8)
|> arc(angleStart = 0, angleEnd = -180, radius = 3)
|> xLine(length = -1.8)
|> xLine(length = -2)
|> yLine(length = -10)
|> xLine(endAbsolute = 0)
|> mirror2d(axis = Y)
|> extrude(length = 2 * segLen(seg02), symmetric = true)
baseUpper = startProfile(coolerBase, at = [0, 10])
|> xLine(length = -0.9)
|> arc(angleStart = 0, angleEnd = 180, radius = 3.1)
|> xLine(length = -1.8)
|> arc(angleStart = 0, angleEnd = 180, radius = 3)
|> xLine(length = -1.8)
|> arc(angleStart = 0, angleEnd = 180, radius = 3)
|> xLine(length = -1.8)
|> xLine(length = -1)
|> yLine(length = 4)
|> tangentialArc(angle = -90, radius = 2)
|> xLine(endAbsolute = 0)
|> mirror2d(axis = Y)
|> extrude(length = 2 * segLen(seg02) * 3 / 4, symmetric = true)
// Create a flexible mounting bracket to secure the heat sink to the motherboard once adhered
mountingBracket = startSketchOn(XZ)
|> startProfile(at = [-10, 16])
|> xLine(length = -20)
|> tangentialArc(angle = 20, radius = bendRadius)
|> angledLine(angle = -160, length = 14, tag = $seg09)
|> tangentialArc(angle = -30, radius = bendRadius + sheetThickness)
|> angledLine(angle = 170, length = 21.5, tag = $seg04Q)
|> angledLine(angle = 170 - 90, length = sheetThickness, tag = $seg08)
|> angledLine(angle = segAng(seg04Q) + 180, length = segLen(seg04Q), tag = $seg05E)
|> tangentialArc(angle = 30, radius = bendRadius)
|> angledLine(angle = segAng(seg09) + 180, length = segLen(seg09))
|> tangentialArc(angle = -20, radius = bendRadius + sheetThickness)
|> xLine(endAbsolute = profileStartX(%))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg07)
|> close()
|> extrude(
length = 16,
symmetric = true,
tagEnd = $capEnd001,
tagStart = $capStart001,
)
|> fillet(
radius = 2,
tags = [
getCommonEdge(faces = [seg07, capEnd001]),
getCommonEdge(faces = [seg08, capEnd001]),
getCommonEdge(faces = [seg08, capStart001]),
getCommonEdge(faces = [seg07, capStart001])
],
)
// Create a clearance hole in the bracket for an M3 screw and countersink the hole
thruHole = startSketchOn(mountingBracket, face = seg05E)
|> circle(center = [70, 0], radius = 3.4 / 2, tag = $seg06E)
|> extrude(length = -sheetThickness)
|> chamfer(
length = sheetThickness * 0.75,
tags = [
getCommonEdge(faces = [seg05E, seg06E])
],
)
// Duplicate the bracket to the other side of the heat sink base
|> patternCircular3d(
instances = 2,
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)

Some files were not shown because too many files have changed in this diff Show More