Compare commits

..

1 Commits

Author SHA1 Message Date
7e49a9c733 fixes
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 15:17:17 -07:00
792 changed files with 46106 additions and 231654 deletions

View File

@ -6,7 +6,6 @@ if [ -z "${TAB_API_URL:-}" ] || [ -z "${TAB_API_KEY:-}" ]; then
fi
project="https://github.com/KittyCAD/modeling-app"
suite="${CI_SUITE:-unit}"
branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
commit="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
@ -14,7 +13,6 @@ echo "Uploading batch results"
curl --silent --request POST \
--header "X-API-Key: ${TAB_API_KEY}" \
--form "project=${project}" \
--form "suite=${suite}" \
--form "branch=${branch}" \
--form "commit=${commit}" \
--form "tests=@test-results/junit.xml" \

View File

@ -123,6 +123,18 @@ jobs:
- id: export_notes
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:
needs: [prepare-files]
@ -247,6 +259,49 @@ jobs:
# 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:
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}}
ZOO_HOST: https://api.dev.zoo.dev
RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Commit differences
if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure'
shell: bash
@ -120,7 +119,6 @@ jobs:
# Configure nextest when it's run by insta (via just).
NEXTEST_PROFILE: ci
RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Build and archive tests
run: |
cd rust
@ -157,7 +155,7 @@ jobs:
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install Rust
- name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
@ -184,7 +182,6 @@ jobs:
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev
RUST_MIN_STACK: 10485760000
- name: Upload results
if: always()
run: .github/ci-cd-scripts/upload-results.sh
@ -193,56 +190,6 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: unit:kcl
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:
name: Run wasm tests
strategy:

View File

@ -40,7 +40,7 @@ jobs:
- name: Install dependencies
run: npm install
- name: Download Wasm cache
- name: Download Wasm Cache
id: download-wasm
if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
@ -52,7 +52,7 @@ jobs:
branch: main
path: rust/kcl-wasm-lib/pkg
- name: Build Wasm condition
- name: Build WASM condition
id: wasm
run: |
set -euox pipefail
@ -70,7 +70,7 @@ jobs:
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install Rust
- name: Install rust
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
@ -81,7 +81,7 @@ jobs:
with:
tool: wasm-pack
- name: Use Rust cache
- name: Rust Cache
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2
with:
@ -117,7 +117,7 @@ jobs:
- uses: actions/download-artifact@v4
name: prepared-wasm
- name: Copy prepared Wasm
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -133,17 +133,20 @@ jobs:
id: deps-install
run: npm install
- name: Cache browsers
- name: Cache Playwright Browsers
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install browsers
- name: Install Playwright Browsers
run: npm run playwright install --with-deps
- name: Test snapshots
- name: build web
run: npm run tronb:vite:dev
- name: Run ubuntu/chrome snapshots
uses: nick-fields/retry@v3.0.2
with:
shell: bash
@ -156,19 +159,6 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: snapshots
TARGET: web
- name: Update snapshots
if: always()
run: npm run test:snapshots -- --last-failed --update-snapshots
env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: snapshots
TARGET: web
- uses: actions/upload-artifact@v4
@ -180,19 +170,20 @@ jobs:
retention-days: 30
overwrite: true
- name: Check diff
- 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 --quiet "Changes to be committed"
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: ${{ steps.git-check.outputs.modified == 'true' }}
- 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
@ -202,7 +193,7 @@ jobs:
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit --message "Update snapshots" || true
git commit -m "A snapshot a day keeps the bugs away! 📷🐛" || true
git push
git push origin ${{ github.head_ref }}

View File

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

View File

@ -4,6 +4,7 @@
# maturin generate-ci github
#
name: kcl-python-bindings
on:
push:
branches:
@ -26,14 +27,16 @@ on:
- '**.rs'
- .github/workflows/kcl-python-bindings.yml
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
linux-x86_64:
name: kcl-python-bindings (linux-x86_64)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -55,8 +58,8 @@ jobs:
with:
name: wheels-linux-x86_64
path: rust/kcl-python-bindings/dist
windows:
name: kcl-python-bindings (windows)
runs-on: windows-16-cores
strategy:
matrix:
@ -81,8 +84,8 @@ jobs:
with:
name: wheels-windows-${{ matrix.target }}
path: rust/kcl-python-bindings/dist
macos:
name: kcl-python-bindings (macos)
runs-on: macos-latest
strategy:
matrix:
@ -107,8 +110,8 @@ jobs:
with:
name: wheels-macos-${{ matrix.target }}
path: rust/kcl-python-bindings/dist
test:
name: kcl-python-bindings (test)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -124,8 +127,8 @@ jobs:
env:
KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
ZOO_HOST: https://api.dev.zoo.dev
sdist:
name: kcl-python-bindings (sdist)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -133,10 +136,10 @@ jobs:
uses: astral-sh/setup-uv@v5
- name: Install codespell
run: |
uv venv .venv
echo "VIRTUAL_ENV=.venv" >> $GITHUB_ENV
echo "$PWD/.venv/bin" >> $GITHUB_PATH
uv pip install pip --upgrade
uv venv .venv
echo "VIRTUAL_ENV=.venv" >> $GITHUB_ENV
echo "$PWD/.venv/bin" >> $GITHUB_PATH
uv pip install pip --upgrade
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
@ -148,6 +151,7 @@ jobs:
with:
name: wheels-sdist
path: rust/kcl-python-bindings/dist
release:
name: Release
runs-on: ubuntu-latest
@ -164,11 +168,11 @@ jobs:
uses: astral-sh/setup-uv@v5
- name: do uv things
run: |
cd rust/kcl-python-bindings
uv venv .venv
echo "VIRTUAL_ENV=.venv" >> $GITHUB_ENV
echo "$PWD/.venv/bin" >> $GITHUB_PATH
uv pip install pip --upgrade
cd rust/kcl-python-bindings
uv venv .venv
echo "VIRTUAL_ENV=.venv" >> $GITHUB_ENV
echo "$PWD/.venv/bin" >> $GITHUB_PATH
uv pip install pip --upgrade
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:

View File

@ -28,7 +28,53 @@ jobs:
- run: npm run fmt:check
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:
runs-on: ubuntu-latest
@ -127,3 +173,122 @@ jobs:
uses: actions/checkout@v4
- name: Run codespell
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/screenshots/main.kcl
/public/kcl-samples/step/main.kcl
/public/kcl-samples/internal
/rust/kcl-lib/tests/kcl_samples/internal
/test-results/
/playwright-report/
/blob-report/

View File

@ -130,7 +130,7 @@ git tag $VERSION
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)).
@ -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.
A prompt should show up asking for a downgrade to the last release version. Running through that at the end of testing
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.
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.
##### Updater-test builds
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.
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)

View File

@ -114,6 +114,7 @@ test-unit: install ## Run the unit tests
npm run test:unit:components
@ 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:kcl-samples
.PHONY: test-e2e
test-e2e: test-e2e-$(TARGET)

View File

@ -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.
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
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

@ -21,7 +21,7 @@ extend-exclude = [
]
[default.extend-words]
metalness = "metalness" # appearance API
metalness = "metalness" # appearance API
Hom = "Hom" # short for homogenous
typ = "typ" # used to declare a variable named 'type' which is a reserved keyword in Rust
ue = "ue" # short for UnaryExpression
@ -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
"ot" = "ot" # some abbreviation, idk what
"oe" = "oe" # some abbreviation, idk what
"colinear" = "colinear" # some engine shit, kidding
[default]
extend-ignore-identifiers-re = [

View File

@ -4,7 +4,7 @@ excerpt: "Documentation of the KCL language for the Zoo Design Studio."
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).
## Topics

View File

@ -27,6 +27,9 @@ import increment from "util.kcl"
answer = increment(41)
```
Imported files _must_ be in the same project so that units are uniform across
modules. This means that it must be in the same directory.
Import statements must be at the top-level of a file. It is not allowed to have
an `import` statement inside a function or in the body of an ifelse.
@ -55,9 +58,6 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
import increment as inc, decrement as dec from "util.kcl"
```
You can import files from the current directory or from subdirectories, but if importing from a
subdirectory you can only import `main.kcl`.
---
## Functions vs `clone`
@ -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.
```norun
import "cube.kcl"
import "tests/inputs/cube.kcl" as cube
cube
|> translate(x=10)
```
@ -229,19 +229,6 @@ The final statement is what's important because it's the return value of the
entire module. The module is expected to return a single object that can be used
as a variable by the file that imports it.
The name of the file or subdirectory is used as the name of the variable within the importing program.
If you want to use a different name, you can do so by using the `as` keyword:
```kcl,norun
import "cube.kcl" // Introduces a new variable called `cube`.
import "cube.kcl" as block // Introduces a new variable called `block`.
import "cube/main.kcl" // Introduces a new variable called `cube`.
import "cube/main.kcl" as block // Introduces a new variable called `block`.
```
If the filename includes hyphens (`-`) or starts with an underscore (`_`), then you must specify a
variable name.
---
## Multiple instances of the same import
@ -254,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.
```norun
import cube from "cube.kcl"
import cube from "tests/inputs/cube.kcl"
cube
|> translate(x=10)
@ -270,7 +257,7 @@ separate objects in memory, and can be manipulated independently.
Here is an example with a file from another CAD system:
```kcl
import "tests/inputs/cube.step"
import "tests/inputs/cube.step" as cube
cube
|> translate(x=10)

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],
initial: any,
f: fn(any, accum: any): any,
): any
): [any]
```
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
[`any`](/docs/kcl-std/types/std-types-any)
[`[any]`](/docs/kcl-std/types/std-types-any)
### 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(
hypotenuse: number(Length),
leg: number(Length),
): number(Length)
): number(deg)
```
@ -25,7 +25,7 @@ legLen(
### Returns
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples

File diff suppressed because one or more lines are too long

View File

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

View File

@ -65,7 +65,7 @@ layout: manual
* [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/loft)
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
* [`polygon`](/docs/kcl-std/polygon)
* [`profileStart`](/docs/kcl-std/profileStart)
* [`profileStartX`](/docs/kcl-std/profileStartX)
@ -94,7 +94,7 @@ layout: manual
* [`intersect`](/docs/kcl-std/intersect)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`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)
* [`subtract`](/docs/kcl-std/subtract)
* [`union`](/docs/kcl-std/union)
@ -128,9 +128,6 @@ layout: manual
* [`E`](/docs/kcl-std/consts/std-math-E)
* [`PI`](/docs/kcl-std/consts/std-math-PI)
* [`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)
* [`turns::HALF_TURN`](/docs/kcl-std/consts/std-turns-HALF_TURN)
* [`turns::QUARTER_TURN`](/docs/kcl-std/consts/std-turns-QUARTER_TURN)

View File

@ -30,7 +30,7 @@ This module contains functions for creating and manipulating sketches, and makin
* [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/loft)
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
* [`polygon`](/docs/kcl-std/polygon)
* [`profileStart`](/docs/kcl-std/profileStart)
* [`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)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`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)
* [`subtract`](/docs/kcl-std/subtract)
* [`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.
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
@ -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)
* [`sketch`](/docs/kcl-std/modules/std-sketch)
* [`solid`](/docs/kcl-std/modules/std-solid)
* [`sweep::sweep`](/docs/kcl-std/modules/std-sweep)
* [`transform`](/docs/kcl-std/modules/std-transform)
* [`turns::turns`](/docs/kcl-std/modules/std-turns)
* [`types`](/docs/kcl-std/modules/std-types)

View File

@ -12,8 +12,8 @@ patternCircular2d(
@sketchSet: [Sketch],
instances: number,
center: Point2d,
arcDegrees?: number,
rotateDuplicates?: bool,
arcDegrees: number,
rotateDuplicates: bool,
useOriginal?: bool,
): [Sketch]
```
@ -27,8 +27,8 @@ patternCircular2d(
| `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 |
| `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 |
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. Defaults to true. | 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. | 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 |
### 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"
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
---
Just like `patternTransform`, but works on 2D sketches not 3D solids.
Just like patternTransform, but works on 2D sketches not 3D solids.
```kcl
patternTransform2d(
@sketches: [Sketch; 1+],
instances: number(_),
transform: fn(number(_)): { },
useOriginal?: boolean,
): [Sketch; 1+]
@sketches: [Sketch],
instances: number,
transform: FunctionSource,
useOriginal?: bool,
): [Sketch]
```
@ -22,14 +22,14 @@ patternTransform2d(
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketches` | [`[Sketch; 1+]`](/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 |
| `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 |
| `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 |
| `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 |
| `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` | [`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
[`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch)
[`[Sketch]`](/docs/kcl-std/types/std-types-Sketch)
### Examples

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

View File

@ -134,6 +134,8 @@ extrude001 = extrude(sketch001, length = 5)`
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await page.waitForTimeout(1000)
// Ensure badge is present
const codePaneButtonHolder = page.locator('#code-button-holder')
await expect(codePaneButtonHolder).toContainText('notification', {
@ -181,7 +183,7 @@ extrude001 = extrude(sketch001, length = 5)`
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
// await scene.settled(cmdBar)
await scene.settled(cmdBar)
// Ensure badge is present
const codePaneButtonHolder = page.locator('#code-button-holder')
@ -235,48 +237,6 @@ extrude001 = extrude(sketch001, length = 5)`
.first()
).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(

View File

@ -36,29 +36,27 @@ test.describe('Command bar tests', () => {
await u.closeDebugPanel()
// Click the line of code for xLine.
await page.getByText(`startProfile(at = [-10, -10])`).click()
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
await page.waitForTimeout(200)
await page.getByText(`close()`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await toolbar.extrudeButton.click()
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Extrude',
currentArgKey: 'length',
currentArgValue: '5',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Profiles: '1 profile',
Sketches: '',
Length: '',
},
highlightedHeaderArg: 'length',
highlightedHeaderArg: 'sketches',
})
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
commandName: 'Extrude',
headerArguments: {
Profiles: '1 profile',
Sketches: '1 segment',
Length: '5',
},
})
@ -288,7 +286,7 @@ test.describe('Command bar tests', () => {
await cmdBar.cmdOptions.getByText('Extrude').click()
// 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
await page.mouse.move(700, 200)
await page.mouse.click(700, 200)
@ -401,6 +399,7 @@ test.describe('Command bar tests', () => {
sortBy: 'last-modified-desc',
})
await page.goto(page.url() + targetURL)
expect(page.url()).toContain(targetURL)
})
await test.step(`Submit the command`, async () => {
@ -411,7 +410,7 @@ test.describe('Command bar tests', () => {
currentArgValue: '',
headerArguments: {
Method: '',
Name: 'main.kcl',
Name: 'test',
Code: '1 line',
},
highlightedHeaderArg: 'method',
@ -422,7 +421,7 @@ test.describe('Command bar tests', () => {
commandName: 'Import file from URL',
headerArguments: {
Method: 'New project',
Name: 'main.kcl',
Name: 'test',
Code: '1 line',
},
})
@ -464,6 +463,7 @@ test.describe('Command bar tests', () => {
sortBy: 'last-modified-desc',
})
await page.goto(page.url() + targetURL)
expect(page.url()).toContain(targetURL)
})
await test.step(`Submit the command`, async () => {
@ -474,7 +474,7 @@ test.describe('Command bar tests', () => {
currentArgValue: '',
headerArguments: {
Method: '',
Name: 'main.kcl',
Name: 'test',
Code: '1 line',
},
highlightedHeaderArg: 'method',
@ -487,7 +487,7 @@ test.describe('Command bar tests', () => {
currentArgValue: '',
headerArguments: {
Method: 'Existing project',
Name: 'main.kcl',
Name: 'test',
ProjectName: '',
Code: '1 line',
},
@ -500,7 +500,7 @@ test.describe('Command bar tests', () => {
headerArguments: {
Method: 'Existing project',
ProjectName: 'testProjectDir',
Name: 'main.kcl',
Name: 'test',
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 editor.expectEditor.toContain('extrusionDistance = 12')
await toolbar.openPane('files')
await toolbar.expectFileTreeState(['main-1.kcl', 'main.kcl'])
await toolbar.expectFileTreeState(['main.kcl', 'test.kcl'])
})
})
@ -661,56 +661,4 @@ 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',
})
})
test('Text-to-CAD command can be closed with escape while in prompt', async ({
page,
homePage,
cmdBar,
}) => {
await homePage.expectState({
projectCards: [],
sortBy: 'last-modified-desc',
})
await homePage.textToCadBtn.click()
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Text-to-CAD Create',
currentArgKey: 'prompt',
currentArgValue: '',
headerArguments: {
Method: 'New project',
NewProjectName: 'untitled',
Prompt: '',
},
highlightedHeaderArg: 'prompt',
})
await page.keyboard.press('Escape')
await cmdBar.toBeClosed()
await cmdBar.expectState({
stage: 'commandBarClosed',
})
})
})

View File

@ -65,9 +65,7 @@ test(
await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`)
await page.waitForTimeout(1_000)
const count = await successToastMessage.count()
await expect(count).toBeGreaterThanOrEqual(1)
await expect(successToastMessage).toBeVisible()
// Check for the exported file
const firstFileFullPath = path.resolve(
@ -136,9 +134,7 @@ test(
await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`)
await page.waitForTimeout(1_000)
const count = await successToastMessage.count()
await expect(count).toBeGreaterThanOrEqual(1)
await expect(successToastMessage).toBeVisible()
await expect(exportingToastMessage).not.toBeVisible()
// Check for the exported file=

View File

@ -1131,15 +1131,14 @@ sketch001 = startSketchOn(XZ)
await page.waitForTimeout(100)
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 cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'length',
currentArgValue: '5',
headerArguments: {
Profiles: '1 profile',
Sketches: '1 face',
Length: '',
},
highlightedHeaderArg: 'length',
@ -1149,7 +1148,7 @@ sketch001 = startSketchOn(XZ)
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Profiles: '1 profile',
Sketches: '1 face',
Length: '5',
},
commandName: 'Extrude',
@ -1355,9 +1354,7 @@ sketch001 = startSketchOn(XZ)
const u = await getUtils(page)
const projectLink = page.getByRole('link', { name: 'cube' })
const gizmo = page.locator('[aria-label*=gizmo]')
const resetCameraButton = page.getByRole('button', {
name: 'Reset view',
})
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
const locationToHaveColor = async (
position: { x: number; y: number },
color: [number, number, number]
@ -1536,6 +1533,7 @@ sketch001 = startSketchOn(XZ)
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
await scene.expectPixelColor(
TEST_COLORS.DARK_MODE_BKGD,

View File

@ -238,26 +238,6 @@ test.describe('when using the file tree to', () => {
}
)
test(
`create new folders and that doesn't trigger a navigation`,
{ tag: ['@electron', '@macos', '@windows'] },
async ({ page, homePage, scene, toolbar, cmdBar }) => {
await homePage.goToModelingScene()
await scene.settled(cmdBar)
await toolbar.openPane('files')
const { createNewFolder } = await getUtils(page, test)
await createNewFolder('folder')
await createNewFolder('folder.kcl')
await test.step(`Postcondition: folders are created and we didn't navigate`, async () => {
await toolbar.expectFileTreeState(['folder', 'folder.kcl', 'main.kcl'])
await expect(toolbar.fileName).toHaveText('main.kcl')
})
}
)
test(
'deleting all files recreates a default main.kcl with no code',
{ tag: '@electron' },

View File

@ -105,19 +105,14 @@ export class CmdBarFixture {
expectState = async (expected: CmdBarSerialised) => {
return expect.poll(() => this._serialiseCmdBar()).toEqual(expected)
}
/**
* This method is used to progress the command bar to the next step, defaulting to clicking the next button.
* Optionally, with the `shouldUseKeyboard` parameter, it will hit `Enter` to progress.
* * TODO: This method assumes the user has a valid input to the current stage,
/** The method will use buttons OR press enter randomly to progress the cmdbar,
* this could have unexpected results depending on what's focused
*
* TODO: This method assumes the user has a valid input to the current stage,
* and assumes we are past the `pickCommand` step.
*/
progressCmdBar = async (shouldUseKeyboard = false) => {
progressCmdBar = async (shouldFuzzProgressMethod = true) => {
await this.page.waitForTimeout(2000)
if (shouldUseKeyboard) {
await this.page.keyboard.press('Enter')
return
}
const arrowButton = this.page.getByRole('button', {
name: 'arrow right Continue',
})
@ -151,7 +146,9 @@ export class CmdBarFixture {
await this.cmdBarOpenBtn.click()
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
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 promptEditCommand.first().scrollIntoViewIfNeeded()
await promptEditCommand.first().click()
@ -313,11 +310,6 @@ export class CmdBarFixture {
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
}
async toBeClosed() {
// Check that the command bar is closed
await expect(this.cmdBarElement).not.toBeVisible({ timeout: 10_000 })
}
async expectArgValue(value: string) {
// Check the placeholder project name exists
const actualArgument = await this.cmdBarElement

View File

@ -26,7 +26,6 @@ export class HomePageFixture {
sortByNameBtn!: Locator
appHeader!: Locator
tutorialBtn!: Locator
textToCadBtn!: Locator
constructor(page: Page) {
this.page = page
@ -48,7 +47,6 @@ export class HomePageFixture {
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.textToCadBtn = this.page.getByTestId('home-text-to-cad')
}
private _serialiseSortBy = async (): Promise<
@ -123,13 +121,11 @@ export class HomePageFixture {
await projectCard.click()
}
/** Returns the project name in case caller has used the default and needs it */
goToModelingScene = async (name = 'testDefault') => {
goToModelingScene = async (name: string = 'testDefault') => {
// 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)
return name
}
isNativeFileMenuCreated = async () => {

View File

@ -61,7 +61,6 @@ class MyAPIReporter implements Reporter {
const payload = {
// Required information
project: 'https://github.com/KittyCAD/modeling-app',
suite: process.env.CI_SUITE || 'e2e',
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
test: test.titlePath().slice(2).join(' '),

View File

@ -197,6 +197,18 @@ test.describe(
await clickElectronNativeMenuById(tronApp, 'File.Export current part')
await cmdBar.expectCommandName('Export')
})
await test.step('Modeling.File.Share part via Zoo link', async () => {
await page.waitForTimeout(250)
await clickElectronNativeMenuById(
tronApp,
'File.Share part via Zoo link'
)
const textToCheck =
'Link copied to clipboard. Anyone who clicks this link will get a copy of this file. Share carefully!'
// Check if text appears anywhere in the page
const isTextVisible = page.getByText(textToCheck)
await expect(isTextVisible).toBeVisible({ timeout: 10000 })
})
await test.step('Modeling.File.Preferences.Project settings', async () => {
await page.waitForTimeout(250)
await clickElectronNativeMenuById(
@ -252,7 +264,7 @@ test.describe(
tronApp,
'Edit.Modify with Zoo Text-To-CAD'
)
await cmdBar.expectCommandName('Text-to-CAD Edit')
await cmdBar.expectCommandName('Prompt-to-edit')
})
await test.step('Modeling.Edit.Edit parameter', async () => {
await page.waitForTimeout(250)
@ -518,7 +530,7 @@ test.describe(
'Design.Create with Zoo Text-To-CAD'
)
await cmdBar.toBeOpened()
await cmdBar.expectCommandName('Text-to-CAD Create')
await cmdBar.expectCommandName('Text to CAD')
})
await test.step('Modeling.Design.Modify with Zoo Text-To-CAD', async () => {
@ -528,7 +540,7 @@ test.describe(
'Design.Modify with Zoo Text-To-CAD'
)
await cmdBar.toBeOpened()
await cmdBar.expectCommandName('Text-to-CAD Edit')
await cmdBar.expectCommandName('Prompt-to-edit')
})
await test.step('Modeling.Help.KCL code samples', async () => {

View File

@ -6,6 +6,7 @@ test.describe('Onboarding tests', () => {
homePage,
toolbar,
editor,
scene,
tronApp,
}) => {
if (!tronApp) {
@ -61,6 +62,7 @@ test.describe('Onboarding tests', () => {
await editor.expectEditor.toContain('@settings(defaultLengthUnit = in)', {
shouldNormalise: true,
})
await scene.connectionEstablished()
})
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 postDismissToast.waitFor({ state: 'hidden' })
await page.keyboard.press('Escape')
await expect(postDismissToast).toBeVisible()
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
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 expect(toolbar.projectName).toContainText('tutorial-project')
await expect(tutorialWelcomeHeading).toBeVisible()
await scene.connectionEstablished()
})
await test.step('Dismiss the onboarding', async () => {
await postDismissToast.waitFor({ state: 'hidden' })
await page.keyboard.press('Escape')
await expect(postDismissToast).toBeVisible()
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
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 toolbar.extrudeButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: { Sketches: '', Length: '' },
highlightedHeaderArg: 'sketches',
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'length',
currentArgValue: '5',
headerArguments: { Profiles: '1 profile', Length: '' },
headerArguments: { Sketches: '1 face', Length: '' },
highlightedHeaderArg: 'length',
commandName: 'Extrude',
})
@ -89,7 +98,7 @@ test.describe('Point-and-click tests', () => {
await cmdBar.expectState({
stage: 'review',
headerArguments: { Profiles: '1 profile', Length: '5' },
headerArguments: { Sketches: '1 face', Length: '5' },
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
@ -1625,15 +1634,15 @@ sketch002 = startSketchOn(plane001)
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: { Profiles: '' },
highlightedHeaderArg: 'Profiles',
headerArguments: { Sketches: '' },
highlightedHeaderArg: 'sketches',
commandName: 'Loft',
})
await selectSketches()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Profiles: '2 profiles' },
headerArguments: { Sketches: '2 faces' },
commandName: 'Loft',
})
await cmdBar.submit()
@ -1645,9 +1654,18 @@ sketch002 = startSketchOn(plane001)
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
await toolbar.loftButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: { Sketches: '' },
highlightedHeaderArg: 'sketches',
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Profiles: '2 profiles' },
headerArguments: { Sketches: '2 faces' },
commandName: 'Loft',
})
await cmdBar.submit()
@ -1812,10 +1830,10 @@ sketch002 = startSketchOn(XZ)
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '',
Sketches: '',
Path: '',
},
highlightedHeaderArg: 'Profiles',
highlightedHeaderArg: 'sketches',
stage: 'arguments',
})
await clickOnSketch1()
@ -1826,7 +1844,7 @@ sketch002 = startSketchOn(XZ)
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '1 profile',
Sketches: '1 face',
Path: '',
},
highlightedHeaderArg: 'path',
@ -1839,7 +1857,7 @@ sketch002 = startSketchOn(XZ)
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '1 profile',
Sketches: '1 face',
Path: '',
},
highlightedHeaderArg: 'path',
@ -1849,17 +1867,13 @@ sketch002 = startSketchOn(XZ)
await cmdBar.expectState({
commandName: 'Sweep',
headerArguments: {
Profiles: '1 profile',
Sketches: '1 face',
Path: '1 segment',
Sectional: '',
},
stage: 'review',
})
// Confirm we can submit from the review step with just `Enter`
await cmdBar.progressCmdBar(true)
await cmdBar.expectState({
stage: 'commandBarClosed',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -1917,51 +1931,63 @@ sketch002 = startSketchOn(XZ)
})
})
test(`Sweep point-and-click helix`, async ({
test(`Sweep point-and-click failing validation`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const circleCode = `circle(sketch001, center = [0, -1], radius = .1)`
const initialCode = `helix001 = helix(
axis = X,
radius = 1,
length = 10,
revolutions = 10,
angleStart = 0,
ccw = false,
)
sketch001 = startSketchOn(XZ)
profile001 = ${circleCode}`
const sweepDeclaration = 'sweep001 = sweep(profile001, path = helix001)'
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(YZ)
|> circle(
center = [0, 0],
radius = 500
)
sketch002 = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> xLine(length = -500)
|> line(endAbsolute = [-2000, 500])
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.settled(cmdBar)
await test.step(`Add sweep through the command bar flow`, async () => {
await toolbar.openPane('feature-tree')
// One dumb hardcoded screen pixel value
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 expect
.poll(() => page.getByText('Please select one').count())
.toBe(1)
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '',
Sketches: '',
Path: '',
},
highlightedHeaderArg: 'Profiles',
highlightedHeaderArg: 'sketches',
stage: 'arguments',
})
await editor.scrollToText(circleCode)
await page.getByText(circleCode).click()
await clickOnSketch1()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Sweep',
@ -1969,45 +1995,17 @@ profile001 = ${circleCode}`
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '1 profile',
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',
Sketches: '1 face',
Path: '',
},
highlightedHeaderArg: 'path',
stage: 'arguments',
})
await clickOnSketch2()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Sweep',
headerArguments: {
Profiles: '1 profile',
Path: '1 helix',
Sectional: '',
},
stage: 'review',
})
await cmdBar.progressCmdBar(true)
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)
await expect(
page.getByText('Unable to sweep with the current selection. Reason:')
).toBeVisible()
})
})
@ -2092,6 +2090,18 @@ extrude001 = extrude(sketch001, length = -12)
await test.step(`Apply fillet to the preselected edge`, async () => {
await page.waitForTimeout(100)
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({
commandName: 'Fillet',
highlightedHeaderArg: 'radius',
@ -2621,6 +2631,18 @@ extrude001 = extrude(profile001, length = 5)
await test.step(`Apply fillet`, async () => {
await page.waitForTimeout(100)
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({
commandName: 'Fillet',
highlightedHeaderArg: 'radius',
@ -2726,6 +2748,19 @@ extrude001 = extrude(sketch001, length = -12)
await test.step(`Apply chamfer to the preselected edge`, async () => {
await page.waitForTimeout(100)
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({
commandName: 'Chamfer',
highlightedHeaderArg: 'length',
@ -3209,6 +3244,8 @@ extrude001 = extrude(sketch001, length = 30)
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
await toolbar.shellButton.click()
await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
@ -3590,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('Base case workflow, auto spam continue in command bar', async ({
context,
@ -3638,17 +3736,16 @@ tag=$rectangleSegmentC002,
await scene.settled(cmdBar)
// select line of code
const codeToSelection = `startProfile(at = [-66.77, 84.81])`
const codeToSelection = `segAng(rectangleSegmentA002) - 90,`
// revolve
await editor.scrollToText(codeToSelection)
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 cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = X)`
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
@ -4577,12 +4674,24 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => {
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({
stage: 'arguments',
currentArgKey: 'length',
currentArgValue: '5',
headerArguments: {
Profiles: '2 profiles',
Sketches: '2 faces',
Length: '',
},
highlightedHeaderArg: 'length',
@ -4593,7 +4702,7 @@ path001 = startProfile(sketch001, at = [0, 0])
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Profiles: '2 profiles',
Sketches: '2 faces',
Length: '1',
},
commandName: 'Extrude',
@ -4659,12 +4768,25 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => {
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({
stage: 'arguments',
currentArgKey: 'path',
currentArgValue: '',
headerArguments: {
Profiles: '2 profiles',
Sketches: '2 faces',
Path: '',
Sectional: '',
},
@ -4677,7 +4799,7 @@ path001 = startProfile(sketch001, at = [0, 0])
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Profiles: '2 profiles',
Sketches: '2 faces',
Path: '1 segment',
Sectional: '',
},
@ -4743,12 +4865,25 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => {
await toolbar.closePane('code')
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({
stage: 'arguments',
currentArgKey: 'axisOrEdge',
currentArgValue: '',
headerArguments: {
Profiles: '2 profiles',
Sketches: '2 faces',
AxisOrEdge: '',
Angle: '',
},
@ -4764,7 +4899,7 @@ path001 = startProfile(sketch001, at = [0, 0])
currentArgKey: 'angle',
currentArgValue: '360',
headerArguments: {
Profiles: '2 profiles',
Sketches: '2 faces',
AxisOrEdge: 'Edge',
Edge: '1 segment',
Angle: '',
@ -4777,7 +4912,7 @@ path001 = startProfile(sketch001, at = [0, 0])
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Profiles: '2 profiles',
Sketches: '2 faces',
AxisOrEdge: 'Edge',
Edge: '1 segment',
Angle: '180',
@ -4808,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

@ -2064,59 +2064,3 @@ test(
})
}
)
test(
'nested dir import works on windows',
{ tag: ['@electron', '@windows'] },
async ({ scene, cmdBar, context, page }, testInfo) => {
// Skip if on non-windows
if (process.platform !== 'win32') {
test.skip()
}
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
const nestedDir = path.join(bracketDir, 'nested')
await fsp.mkdir(nestedDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
path.join(nestedDir, 'main.kcl')
)
await fsp.writeFile(
path.join(bracketDir, 'main.kcl'),
`import 'nested\\main.kcl' as thing
thing`
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await scene.settled(cmdBar)
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
}
)

View File

@ -99,8 +99,6 @@ test.describe('edit with AI example snapshots', () => {
await test.step('fire off edit prompt', async () => {
await cmdBar.captureTextToCadRequestSnapshot(test.info())
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
await page
.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 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
await page
.getByTestId('cmd-bar-arg-value')
@ -167,8 +165,6 @@ test.describe('Prompt-to-edit tests', () => {
await test.step('fire of bad prompt', async () => {
await cmdBar.openCmdBar('promptToEdit')
await page.waitForTimeout(100)
await cmdBar.progressCmdBar()
await page
.getByTestId('cmd-bar-arg-value')
.fill('ansheusha asnthuatshoeuhtaoetuhthaeu laughs in dvorak')

View File

@ -19,12 +19,11 @@ test.describe('Regression tests', () => {
context,
page,
homePage,
scene,
}) => {
// 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
// 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 () => {
localStorage.setItem(
'persistCode',
@ -41,8 +40,7 @@ test.describe('Regression tests', () => {
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.connectionEstablished()
// await u.waitForPageLoad()
await u.waitForPageLoad()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
@ -190,8 +188,8 @@ extrude001 = extrude(sketch001, length = 50)
page.locator('.pretty-json-container >> text=myVar:"67')
).toBeVisible()
})
test('ProgramMemory can be serialised', async ({ page, homePage, scene }) => {
// const u = await getUtils(page)
test('ProgramMemory can be serialised', async ({ page, homePage }) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
@ -216,12 +214,11 @@ extrude001 = extrude(sketch001, length = 50)
// Listen for all console events and push the message text to an array
page.on('console', (message) => messages.push(message.text()))
await homePage.goToModelingScene()
// await u.waitForPageLoad()
await scene.connectionEstablished()
await u.waitForPageLoad()
// wait for execution done
// await u.openDebugPanel()
// await u.expectCmdLog('[data-message-type="execution-done"]')
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
const forbiddenMessages = ['cannot serialize tagged newtype variant']
forbiddenMessages.forEach((forbiddenMessage) => {
@ -235,7 +232,6 @@ extrude001 = extrude(sketch001, length = 50)
context,
page,
homePage,
scene,
}) => {
const u = await getUtils(page)
// const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -254,10 +250,11 @@ extrude001 = extrude(sketch001, length = 50)
shell(exampleSketch, faces = ['end'], thickness = 0.25)`
)
})
await homePage.goToModelingScene()
await scene.connectionEstablished()
await expect(async () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
timeout: 1_000,
@ -458,10 +455,12 @@ extrude002 = extrude(profile002, length = 150)
// Click the stl.
await expect(stlOption).toBeVisible()
await page.keyboard.press('Enter')
// Click the checkbox
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
// Find the toast.
@ -469,13 +468,11 @@ extrude002 = extrude(profile002, length = 150)
await expect(exportingToastMessage).toBeVisible()
// Expect it to succeed.
await expect(exportingToastMessage).not.toBeVisible()
await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`)
await page.waitForTimeout(1_000)
const count = await successToastMessage.count()
await expect(count).toBeGreaterThanOrEqual(1)
await expect(successToastMessage).toBeVisible()
}
)
// We updated this test such that you can have multiple exports going at once.

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.closeDebugPanel()
// click profile in code
await page.getByText(`startProfile(at = [-0.45, 0.87])`).click()
// click "line(end = [1.32, 0.38])"
await page.getByText(`line(end = [1.32, 0.38])`).click()
await page.waitForTimeout(100)
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
{ timeout: 10_000 }
@ -1014,13 +1014,14 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
// click extrude
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.
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'length',
currentArgValue: '5',
headerArguments: { Profiles: '1 profile', Length: '' },
headerArguments: { Sketches: '1 segment', Length: '' },
highlightedHeaderArg: 'length',
commandName: 'Extrude',
})
@ -1364,18 +1365,18 @@ solid001 = subtract([extrude001], tools = [extrude002])
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`fn in2mm(@inches) {
`fn in2mm = (inches) => {
return inches * 25.4
}
railTop = in2mm(.748)
railSide = in2mm(.024)
railBaseWidth = in2mm(.612)
railWideWidth = in2mm(.835)
railBaseLength = in2mm(.200)
railClampable = in2mm(.200)
const railTop = in2mm(.748)
const railSide = in2mm(.024)
const railBaseWidth = in2mm(.612)
const railWideWidth = in2mm(.835)
const railBaseLength = in2mm(.200)
const railClampable = in2mm(.200)
rail = startSketchOn(XZ)
const rail = startSketchOn(XZ)
|> startProfile(at = [-railTop / 2, railClampable + railBaseLength])
|> line(endAbsolute = [
railTop / 2,
@ -3539,6 +3540,7 @@ profile001 = startProfile(sketch001, at = [127.56, 179.02])
await homePage.openProject('multi-file-sketch-test')
await scene.connectionEstablished()
await scene.settled(cmdBar)
await u.closeDebugPanel()
@ -3553,6 +3555,9 @@ profile001 = startProfile(sketch001, at = [127.56, 179.02])
await toolbar.openFile('error.kcl')
// Ensure filetree is populated
await scene.settled(cmdBar)
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(0)

View File

@ -1,3 +1,4 @@
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
@ -8,7 +9,6 @@ import {
settingsToToml,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
test.beforeEach(async ({ page, context }) => {
// 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)
await context.addInitScript(async () => {
localStorage.setItem(
@ -784,7 +784,7 @@ test('theme persists', async ({ page, context, homePage }) => {
await page.setViewportSize({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await u.waitForAuthSkipAppStart()
await page.waitForTimeout(500)
// 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
// Expect the network to be down
await expect(networkToggle).toContainText('Problem')
await expect(networkToggle).toContainText('Offline')
// simulate network up
await u.emulateNetworkConditions({
@ -873,50 +873,6 @@ sweepSketch = startSketchOn(XY)
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 ({
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,257 +1,236 @@
import type { EngineCommand } from '@src/lang/std/artifactGraph'
import { uuidv4 } from '@src/lib/utils'
import {
commonPoints,
getUtils,
TEST_COLORS,
circleMove,
} from '@e2e/playwright/test-utils'
import { commonPoints, getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Test network related behaviors', () => {
test(
'simulate network down and network little widget',
{ tag: '@skipLocalEngine' },
async ({ page, homePage }) => {
const networkToggleConnectedText = page.getByText('Connected')
const networkToggleWeakText = page.getByText('Network health (Weak)')
test.describe(
'Test network and connection issues',
{
tag: ['@macos', '@windows'],
},
() => {
test(
'simulate network down and network little widget',
{ tag: '@skipLocalEngine' },
async ({ page, homePage }) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await homePage.goToModelingScene()
const networkToggle = page.getByTestId('network-toggle')
const networkToggle = page.getByTestId('network-toggle')
// This is how we wait until the stream is online
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled({ timeout: 15000 })
// This is how we wait until the stream is online
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled({ timeout: 15000 })
const networkWidget = page.locator('[data-testid="network-toggle"]')
await expect(networkWidget).toBeVisible()
await networkWidget.hover()
const networkWidget = page.locator('[data-testid="network-toggle"]')
await expect(networkWidget).toBeVisible()
await networkWidget.hover()
const networkPopover = page.locator('[data-testid="network-popover"]')
await expect(networkPopover).not.toBeVisible()
const networkPopover = page.locator('[data-testid="network-popover"]')
await expect(networkPopover).not.toBeVisible()
// (First check) Expect the network to be up
await expect(networkToggle).toContainText('Connected')
// (First check) Expect the network to be up
await expect(
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
// Click the network widget
await networkWidget.click()
// Click the network widget
await networkWidget.click()
// Check the modal opened.
await expect(networkPopover).toBeVisible()
// Check the modal opened.
await expect(networkPopover).toBeVisible()
// Click off the modal.
await page.mouse.click(100, 100)
await expect(networkPopover).not.toBeVisible()
// Click off the modal.
await page.mouse.click(100, 100)
await expect(networkPopover).not.toBeVisible()
// Turn off the network
await u.emulateNetworkConditions({
offline: true,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
// Turn off the network
await u.emulateNetworkConditions({
offline: true,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
// Expect the network to be down
await expect(networkToggle).toContainText('Problem')
// Expect the network to be down
await expect(networkToggle).toContainText('Problem')
// Click the network widget
await networkWidget.click()
// Click the network widget
await networkWidget.click()
// Check the modal opened.
await expect(networkPopover).toBeVisible()
// Check the modal opened.
await expect(networkPopover).toBeVisible()
// Click off the modal.
await page.mouse.click(0, 0)
await expect(networkPopover).not.toBeVisible()
// Click off the modal.
await page.mouse.click(0, 0)
await expect(networkPopover).not.toBeVisible()
// Turn back on the network
await u.emulateNetworkConditions({
offline: false,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
// Turn back on the network
await u.emulateNetworkConditions({
offline: false,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled({ timeout: 15000 })
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled({ timeout: 15000 })
// (Second check) expect the network to be up
await expect(networkToggle).toContainText('Connected')
}
)
// (Second check) expect the network to be up
await expect(
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
}
)
test(
'Engine disconnect & reconnect in sketch mode',
{ tag: '@skipLocalEngine' },
async ({ page, homePage, toolbar, scene, cmdBar }) => {
const networkToggle = page.getByTestId('network-toggle')
test(
'Engine disconnect & reconnect in sketch mode',
{ tag: '@skipLocalEngine' },
async ({ page, homePage, toolbar, scene, cmdBar }) => {
const networkToggle = page.getByTestId('network-toggle')
const networkToggleConnectedText = page.getByText('Connected')
const networkToggleWeakText = page.getByText('Network health (Weak)')
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
await homePage.goToModelingScene()
await u.waitForPageLoad()
await homePage.goToModelingScene()
await u.waitForPageLoad()
await u.openDebugPanel()
// click on "Start Sketch" button
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
await u.openDebugPanel()
// click on "Start Sketch" button
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
// select a plane
await page.mouse.click(700, 200)
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn(XZ)`
)
await u.closeDebugPanel()
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)`
)
await u.closeDebugPanel()
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn(XZ)profile001 = startProfile(sketch001, at = ${commonPoints.startAt})`
)
await page.waitForTimeout(100)
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfile(sketch001, at = ${commonPoints.startAt})`
)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
await expect(
page.locator('.cm-content')
).toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfile(sketch001, at = ${commonPoints.startAt})
await expect(
page.locator('.cm-content')
).toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfile(sketch001, at = ${commonPoints.startAt})
|> xLine(length = ${commonPoints.num1})`)
// Expect the network to be up
await networkToggle.hover()
await expect(
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
// Expect the network to be up
await expect(networkToggle).toContainText('Connected')
// simulate network down
await u.emulateNetworkConditions({
offline: true,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
// simulate network down
await u.emulateNetworkConditions({
offline: true,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
// Expect the network to be down
await networkToggle.hover()
await expect(networkToggle).toContainText('Problem')
// Expect the network to be down
await expect(networkToggle).toContainText('Problem')
// Ensure we are not in sketch mode
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).not.toBeVisible()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// Ensure we are not in sketch mode
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).not.toBeVisible()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// simulate network up
await u.emulateNetworkConditions({
offline: false,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
// simulate network up
await u.emulateNetworkConditions({
offline: false,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
// Wait for the app to be ready for use
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled({ timeout: 15000 })
// Wait for the app to be ready for use
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled({ timeout: 15000 })
// Expect the network to be up
await networkToggle.hover()
await expect(
networkToggleConnectedText.or(networkToggleWeakText)
).toBeVisible()
// Expect the network to be up
await expect(networkToggle).toContainText('Connected')
await scene.settled(cmdBar)
await scene.settled(cmdBar)
// Click off the code pane.
await page.mouse.click(100, 100)
// Click off the code pane.
await page.mouse.click(100, 100)
// select a line
await page
.getByText(`startProfile(sketch001, at = ${commonPoints.startAt})`)
.click()
// select a line
await page
.getByText(`startProfile(sketch001, at = ${commonPoints.startAt})`)
.click()
// enter sketch again
await toolbar.editSketch()
// enter sketch again
await toolbar.editSketch()
// Click the line tool
await page
.getByRole('button', { name: 'line Line', exact: true })
.click()
// Click the line tool
await page.getByRole('button', { name: 'line Line', exact: true }).click()
await page.waitForTimeout(150)
await page.waitForTimeout(150)
const camCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 109, y: 0, z: -152 },
vantage: { x: 115, y: -505, z: -152 },
up: { x: 0, y: 0, z: 1 },
},
}
const updateCamCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
}
await toolbar.openPane('debug')
await u.sendCustomCmd(camCommand)
await page.waitForTimeout(100)
await u.sendCustomCmd(updateCamCommand)
await page.waitForTimeout(100)
const camCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 109, y: 0, z: -152 },
vantage: { x: 115, y: -505, z: -152 },
up: { x: 0, y: 0, z: 1 },
},
}
const updateCamCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
}
await toolbar.openPane('debug')
await u.sendCustomCmd(camCommand)
await page.waitForTimeout(100)
await u.sendCustomCmd(updateCamCommand)
await page.waitForTimeout(100)
// click to continue profile
await page.mouse.click(1007, 400)
await page.waitForTimeout(100)
// Ensure we can continue sketching
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect
.poll(u.normalisedEditorCode)
.toBe(`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
// click to continue profile
await page.mouse.click(1007, 400)
await page.waitForTimeout(100)
// Ensure we can continue sketching
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect
.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [12.34, -12.34])
|> xLine(length = 12.34)
|> line(end = [-12.34, 12.34])
`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect
.poll(u.normalisedEditorCode)
.toBe(`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
await expect
.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [12.34, -12.34])
|> xLine(length = 12.34)
|> line(end = [-12.34, 12.34])
@ -259,105 +238,22 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34])
`)
// Unequip line tool
await page.keyboard.press('Escape')
// Make sure we didn't pop out of sketch mode.
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
await expect(
page.getByRole('button', { name: 'line Line', exact: true })
).not.toHaveAttribute('aria-pressed', 'true')
// Unequip line tool
await page.keyboard.press('Escape')
// Make sure we didn't pop out of sketch mode.
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
await expect(
page.getByRole('button', { name: 'line Line', exact: true })
).not.toHaveAttribute('aria-pressed', 'true')
// Exit sketch
await page.keyboard.press('Escape')
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).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()
// Exit sketch
await page.keyboard.press('Escape')
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).not.toBeVisible()
}
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

@ -44,8 +44,6 @@ export const lowerRightMasks = (page: Page) => [
export type TestColor = [number, number, number]
export const TEST_COLORS: { [key: string]: TestColor } = {
WHITE: [249, 249, 249],
OFFWHITE: [237, 237, 237],
GREY: [142, 142, 142],
YELLOW: [255, 255, 0],
BLUE: [0, 0, 255],
DARK_MODE_BKGD: [27, 27, 27],
@ -551,20 +549,17 @@ export async function getUtils(page: Page, test_?: typeof test) {
createNewFile: async (name: string) => {
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('tree-input-field').fill(name)
await page.keyboard.press('Enter')
})
},
createNewFolder: async (name: string) => {
return test?.step(`Create a folder named ${name}`, async () => {
await page.getByTestId('create-folder-button').click()
await page.getByTestId('tree-input-field').fill(name)
await page.keyboard.press('Enter')
})
},
cloneFile: async (name: string) => {
return test?.step(`Cloning file '${name}'`, async () => {
await page

View File

@ -4,10 +4,9 @@ import type { Page } from '@playwright/test'
import { createProject, getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
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)
await test.step('Set up', async () => {
@ -16,11 +15,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad()
})
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
// Find the toast.
// 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 ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -70,11 +64,7 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x6 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x6 lego')
// Find the toast.
// Look out for the toast message
@ -92,11 +82,7 @@ test.describe('Text-to-CAD tests', () => {
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
// Can send a new prompt from the command bar.
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
// Find the toast.
// 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 ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -123,11 +108,7 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
// Find the toast.
// 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 ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -170,11 +150,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad()
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
await sendPromptFromCommandBarAndSetExistingProject(
page,
randomPrompt,
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, randomPrompt)
// Find the toast.
// 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 ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -222,7 +197,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad()
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
// Find the toast.
// 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 ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -291,7 +265,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad()
const badPrompt = 'akjsndladflajbhflauweyf15;'
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
// Find the toast.
// 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()
// They should be able to try again from the command bar.
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
// Find the toast.
// 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 ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1000, height: 500 })
const projectName = await homePage.goToModelingScene()
await homePage.goToModelingScene()
await u.waitForPageLoad()
const promptWithNewline = `a 2x4\nlego`
await test.step('Get to the prompt step to test', async () => {
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: '',
},
})
})
await page.getByTestId('text-to-cad').click()
// Type the prompt.
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 ({
page,
homePage,
cmdBar,
}) => {
// Let this test run longer since we've seen it timeout.
test.setTimeout(180_000)
@ -419,23 +365,11 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x8 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x8 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x10 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x10 lego')
// Find the toast.
// 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 ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -515,16 +448,11 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
await sendPromptFromCommandBarAndSetExistingProject(
await sendPromptFromCommandBarTriggeredByButton(
page,
'alkjsdnlajshdbfjlhsbdf a;askjdnf',
cmdBar
'alkjsdnlajshdbfjlhsbdf a;askjdnf'
)
// Find the toast.
@ -598,9 +526,7 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
const cmdSearchBar = page.getByPlaceholder('Search commands')
await expect(cmdSearchBar).toBeVisible()
const textToCadCommand = page.getByRole('option', {
name: 'Text-to-CAD Create',
})
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API')
await expect(textToCadCommand.first()).toBeVisible()
// Click the Text-to-CAD command
await textToCadCommand.first().scrollIntoViewIfNeeded()
@ -618,67 +544,29 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
})
}
async function sendPromptFromCommandBarAndSetExistingProject(
async function sendPromptFromCommandBarTriggeredByButton(
page: Page,
promptStr: string,
cmdBar: CmdBarFixture,
projectName = 'testDefault'
promptStr: string
) {
await page.waitForTimeout(1000)
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
await cmdBar.openCmdBar()
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
await page.getByTestId('text-to-cad').click()
await cmdBar.expectState({
commandName: 'Text-to-CAD Create',
stage: 'arguments',
currentArgKey: 'method',
currentArgValue: '',
highlightedHeaderArg: 'method',
headerArguments: {
Method: '',
Prompt: '',
},
})
await cmdBar.currentArgumentInput.fill('existing')
await cmdBar.progressCmdBar()
// Enter the prompt.
const prompt = page.getByRole('textbox', { name: 'Prompt' })
await expect(prompt.first()).toBeVisible()
await cmdBar.expectState({
commandName: 'Text-to-CAD Create',
stage: 'arguments',
currentArgKey: 'projectName',
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()
// Type the prompt.
await page.keyboard.type(promptStr)
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
})
}
test(
'Text-to-CAD functionality',
{ tag: '@electron' },
async ({ context, page, cmdBar }, testInfo) => {
async ({ context, page }, testInfo) => {
const projectName = 'project-000'
const prompt = 'lego 2x4'
const textToCadFileName = 'lego-2x4.kcl'
@ -715,12 +603,7 @@ test(
await openKclCodePanel()
await test.step(`Test file creation`, async () => {
await sendPromptFromCommandBarAndSetExistingProject(
page,
prompt,
cmdBar,
projectName
)
await sendPromptFromCommandBarTriggeredByButton(page, prompt)
// File is considered created if it shows up in the Project Files pane
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
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')).toContainText(
'main.kcl'
'2x2x2-cube.kcl'
)
await u.openFilePanel()
await expect(
page.getByTestId('file-tree-item').getByText('main.kcl')
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).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')).toContainText(
'main.kcl'
'2x2x2-cube.kcl'
)
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')).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')).toContainText(
'main.kcl'
'2x2x2-cube.kcl'
)
// Check file is created
await u.openFilePanel()
await expect(
page.getByTestId('file-tree-item').getByText('main.kcl')
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).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 page.waitForTimeout(100)
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await expect(page.getByText('Confirm Extrude')).toBeVisible()
await cmdBar.progressCmdBar()

View File

@ -94,6 +94,7 @@
"build:wasm:dev": "./scripts/build-wasm-dev.sh",
"build:wasm:dev:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/build-wasm-dev.ps1",
"pretest": "npm run remove-importmeta",
"test:rust": "(cd rust && just test && just lint)",
"simpleserver": "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 &",
@ -129,14 +130,15 @@
"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": "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: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: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: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: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": {
"production": [

View File

@ -37,8 +37,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![bottle](screenshots/bottle.png)](bottle/main.kcl)
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
[![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](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl)
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))
@ -51,16 +49,12 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![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](screenshots/cycloidal-gear.png)](cycloidal-gear/main.kcl)
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
[![dodecahedron](screenshots/dodecahedron.png)](dodecahedron/main.kcl)
#### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png))
[![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](screenshots/exhaust-manifold.png)](exhaust-manifold/main.kcl)
#### [flange](flange/main.kcl) ([screenshot](screenshots/flange.png))
@ -109,8 +103,6 @@ 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)
#### [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)
#### [pdu-faceplate](pdu-faceplate/main.kcl) ([screenshot](screenshots/pdu-faceplate.png))
[![pdu-faceplate](screenshots/pdu-faceplate.png)](pdu-faceplate/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))
@ -127,24 +119,16 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![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](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](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](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))

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,155 +0,0 @@
// Curtain Wall Anchor Plate
// A structural steel L-plate used to anchor curtain wall systems to concrete slabs, with elongated holes for adjustability and bolts with nuts and base plates for secure fastening
// Set units in millimeters (mm)
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
// Define parameters
slabPlateBaseLength = 300
slabPlateHookLength = 80
slabPlateWidth = 200
slabPlateThickness = 8
offsetSlabRail = 200
// Generate L-shaped anchor profile with base and hook flange
// Includes fillets at internal and external corners for strength and safety
fn lProfileFn(lengthBase, lengthHook, width, thickness) {
profilePlane = startSketchOn(offsetPlane(XZ, offset = -width / 2))
profileShape = startProfile(profilePlane, at = [0, 0])
|> yLine(length = lengthHook, tag = $hookOutside)
|> xLine(length = thickness)
|> yLine(length = thickness - lengthHook, tag = $hookInside)
|> xLine(length = lengthBase - thickness, tag = $baseInside)
|> yLine(length = -thickness)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $baseOutside)
|> close()
profileBody = extrude(profileShape, length = width)
|> fillet(
radius = thickness,
tags = [
getCommonEdge(faces = [baseInside, hookInside])
],
)
|> fillet(
radius = thickness * 2,
tags = [
getCommonEdge(faces = [baseOutside, hookOutside])
],
)
return profileBody
}
// Create a hexagonal shape used for bolt and nut heads
fn hexagonFn(plane, radius) {
shape = startProfile(plane, at = [-radius, 0])
|> angledLine(angle = 60, length = radius)
|> xLine(length = radius)
|> angledLine(angle = -60, length = radius)
|> angledLine(angle = -120, length = radius)
|> xLine(length = -radius)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
return shape
}
// Build a bolt with a hexagonal head and cylindrical shaft
fn boltFn(diameter, length) {
boltHeadPlane = startSketchOn(XY)
boltHeadShape = hexagonFn(plane = boltHeadPlane, radius = diameter)
boltHeadBody = extrude(boltHeadShape, length = diameter * 0.7)
boltPlane = startSketchOn(boltHeadBody, face = START)
boltShape = circle(boltPlane, center = [0, 0], radius = diameter / 2)
boltBody = extrude(boltShape, length = length)
return boltBody
}
// Construct a bolt assembly with base plate and hex nut
// Assembles all parts for realistic anchor simulation
fn boltWithPlateAndNutFn(diameter, length, gap) {
plateSide = diameter * 3
plateplane = startSketchOn(offsetPlane(XY, offset = -gap))
plateShape = startProfile(plateplane, at = [-plateSide / 2, -plateSide / 2])
|> yLine(length = plateSide)
|> xLine(length = plateSide)
|> yLine(length = -plateSide)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
plateBody = extrude(plateShape, length = -diameter * 0.3)
nutPlane = startSketchOn(plateBody, face = START)
boltHeadShape = hexagonFn(plane = nutPlane, radius = 12)
boltHeadBody = extrude(boltHeadShape, length = diameter * 0.7)
boltBody = boltFn(diameter = diameter, length = gap + diameter + 3)
mergedBody = union([boltHeadBody, boltBody])
return mergedBody
}
// Generate the plate geometry with a vertical hook for slab attachment
slabPlate = lProfileFn(
lengthBase = slabPlateBaseLength,
lengthHook = slabPlateHookLength,
width = slabPlateWidth,
thickness = slabPlateThickness,
)
// Define oblong holes for bolts, allowing positional adjustment
wideHoleWidth = 12
wideHoleLength = 60
wideHoleOffset = 30
// Two slots mirrored across the plate width
wideHolePlane = startSketchOn(XY)
wideHoleShape = startProfile(
wideHolePlane,
at = [
-(wideHoleLength - wideHoleWidth) / 2,
wideHoleWidth / 2
],
)
|> xLine(length = wideHoleLength - wideHoleWidth)
|> tangentialArc(endAbsolute = [
(wideHoleLength - wideHoleWidth) / 2,
-wideHoleWidth / 2
])
|> xLine(length = wideHoleWidth - wideHoleLength)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> translate(
%,
x = offsetSlabRail,
y = wideHoleOffset - (slabPlateWidth / 2),
z = -1,
)
wideHoleVoidLeft = extrude(wideHoleShape, length = slabPlateThickness + 2)
wideHoleVoidRight = clone(wideHoleVoidLeft)
|> translate(
%,
x = 0,
y = slabPlateWidth - (wideHoleOffset * 2),
z = 0,
)
// Cut the holes into the anchor plate body
slabPlatePunchOne = subtract([slabPlate], tools = [wideHoleVoidLeft])
slabPlatePunchTwo = subtract([slabPlatePunchOne], tools = [wideHoleVoidRight])
// Add two bolt assemblies into the oblong slots
// Properly rotated and spaced to match anchor hole layout
slabPlateBolts = boltWithPlateAndNutFn(diameter = 10, length = 20, gap = slabPlateThickness + 5)
|> rotate(
%,
roll = 180,
pitch = 0,
yaw = 0,
)
|> translate(
%,
x = offsetSlabRail,
y = wideHoleOffset - (slabPlateWidth / 2),
z = 5,
)
|> patternLinear3d(
%,
instances = 2,
distance = slabPlateWidth - (wideHoleOffset * 2),
axis = [0, -1, 0],
)

View File

@ -1,79 +0,0 @@
// Engine Valve
// A mechanical valve used in internal combustion engines to control intake or exhaust flow
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
// Define parameters
valveDiameter = 30
valveLength = 120
valveHeadLength = valveDiameter * 1.0
valveHeadThickness = 3
stemDiameter = 6
stemHeadLength = 9
stemLength = valveLength - valveHeadLength - stemHeadLength
// Create the valve head
valveRadius = valveDiameter / 2
valveHeadPlane = startSketchOn(XZ)
valveHeadShape = startProfile(valveHeadPlane, at = [-0.01, valveHeadLength])
|> xLine(length = 0.01 - (stemDiameter / 2))
|> line(endAbsolute = [0.01 - (stemDiameter / 2), valveRadius])
|> tangentialArc(endAbsolute = [-0.8 * valveRadius, valveHeadThickness], tag = $seg01)
|> tangentialArc(endAbsolute = [-valveRadius, 0])
|> xLine(length = 0.3 * valveRadius)
|> arc(
interiorAbsolute = [
-0.34 * valveRadius,
0.08 * valveRadius
],
endAbsolute = [
-0.02 * valveRadius,
0.11 * valveRadius
],
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
valveHead = revolve(valveHeadShape, angle = 360, axis = Y)
// Create the valve stem
valveStemSketch = startSketchOn(offsetPlane(XY, offset = valveHeadLength))
|> circle(center = [0, 0], radius = stemDiameter / 2)
|> extrude(length = stemLength - valveHeadLength - stemHeadLength)
// Create the valve stem end
stepLength = stemHeadLength / 10
step1 = startSketchOn(valveStemSketch, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|> extrude(%, length = stepLength * 2)
step2 = startSketchOn(step1, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|> extrude(%, length = stepLength)
step3 = startSketchOn(step2, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|> extrude(%, length = stepLength)
step4 = startSketchOn(step3, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|> extrude(%, length = stepLength)
step5 = startSketchOn(step4, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|> extrude(%, length = stepLength)
step6 = startSketchOn(step5, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|> extrude(%, length = stepLength)
step7 = startSketchOn(step6, face = END)
|> circle(
%,
center = [0, 0],
radius = stemDiameter / 2 * 0.9,
tag = $seg02,
)
|> extrude(%, length = stepLength * 3, tagEnd = $capEnd001)
|> chamfer(
length = 0.5,
tags = [
getCommonEdge(faces = [seg02, capEnd001])
],
)

View File

@ -74,16 +74,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "brake-rotor/main.kcl",
"multipleFiles": false,
"title": "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.",
"description": "",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",
@ -157,16 +147,6 @@
"removable-sticker.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "curtain-wall-anchor-plate/main.kcl",
"multipleFiles": false,
"title": "Curtain Wall Anchor Plate",
"description": "A structural steel L-plate used to anchor curtain wall systems to concrete slabs, with elongated holes for adjustability and bolts with nuts and base plates for secure fastening",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "cycloidal-gear/main.kcl",
@ -197,16 +177,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "engine-valve/main.kcl",
"multipleFiles": false,
"title": "Engine Valve",
"description": "A mechanical valve used in internal combustion engines to control intake or exhaust flow",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "exhaust-manifold/main.kcl",
@ -452,16 +422,6 @@
"robot-rotating-base.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "pdu-faceplate/main.kcl",
"multipleFiles": false,
"title": "Power Distribution Unit (PDU) faceplate with European plug sockets and switch",
"description": "Designed for standard 19-inch rack systems with 1U height and 8 sockets",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "pillow-block-bearing/main.kcl",
@ -552,16 +512,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "sash-window/main.kcl",
"multipleFiles": false,
"title": "Sash Window",
"description": "A traditional wooden sash window with two vertically sliding panels and a central locking mechanism",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
@ -572,16 +522,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "shepherds-hook-bolt/main.kcl",
"multipleFiles": false,
"title": "Shepherds Hook Bolt",
"description": "A bent bolt with a curved hook, typically used for hanging or anchoring loads. The threaded end allows secure attachment to surfaces or materials, while the curved hook resists pull-out under tension.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
@ -592,16 +532,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "spinning-highrise-tower/main.kcl",
"multipleFiles": false,
"title": "Spinning Highrise Tower",
"description": "A conceptual high-rise tower with a central core and rotating floor slabs, demonstrating dynamic form through vertical repetition and transformation",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "spur-gear/main.kcl",
@ -632,16 +562,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "thermal-block-insert/main.kcl",
"multipleFiles": false,
"title": "Thermal Block Insert",
"description": "Interlocking insulation insert for masonry walls, designed with a tongue-and-groove profile for modular alignment and thermal efficiency",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "tooling-nest-block/main.kcl",

View File

@ -1,240 +0,0 @@
// Power Distribution Unit (PDU) faceplate with European plug sockets and switch
// Designed for standard 19-inch rack systems with 1U height and 8 sockets
// Set units in millimeters (mm)
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
// Define the dimensions
// Width fits standard 19” rack, height is 1U, depth is variable
faceplateWidth = 482.6 // this is standardized to fit 19-inch racks)
faceplateHeight = 44.45 // usually 1U (44.45 mm), but can be 2U (88.9 mm) or more
faceplateDepth = 100 // varies by manufacturer, but commonly between 100 mm and 300 mm
// Define dimensions of side supports (width and thickness)
supportWidth = 50
supportThickness = 3
// Main body of the PDU faceplate with integrated rack mounting flanges
faceplateShape = startSketchOn(offsetPlane(XY, offset = -faceplateHeight / 2))
|> startProfile(%, at = [-faceplateWidth / 2 - supportWidth, 0])
|> yLine(length = supportThickness)
|> xLine(length = supportWidth)
|> yLine(length = faceplateDepth - supportThickness)
|> xLine(length = faceplateWidth)
|> yLine(length = supportThickness - faceplateDepth)
|> xLine(length = supportWidth)
|> yLine(length = -supportThickness)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|> close()
faceplateBody = extrude(faceplateShape, length = faceplateHeight)
faceplateFrontFace = startSketchOn(faceplateBody, face = seg01)
// Creates recessed volume within the faceplate for inserting modules
nestWall = 2
nestWidth = faceplateWidth - (nestWall * 2)
nestHeight = faceplateHeight - (nestWall * 2)
nestDepth = faceplateDepth - nestWall
nestShape = startProfile(faceplateFrontFace, at = [-nestWidth / 2, nestHeight / 2])
|> xLine(length = nestWidth)
|> yLine(length = -nestHeight)
|> xLine(length = -nestWidth)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
nestVoid = extrude(nestShape, length = -nestDepth)
// Spacer block on the left side, used to position components correctly
moduleHeight = nestHeight
moduleWidth = nestHeight
moduleDepth = nestHeight
leftSpacerWidth = moduleWidth * 1.5
leftSpacerPosition = leftSpacerWidth / 2 - (nestWidth / 2)
fn boxModuleFn(width) {
shape = startSketchOn(XZ)
|> startProfile(%, at = [-width / 2, moduleHeight / 2])
|> xLine(length = width)
|> yLine(length = -moduleHeight)
|> xLine(length = -width)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
body = extrude(shape, length = -moduleDepth)
return body
}
leftSpacerShape = boxModuleFn(width = leftSpacerWidth)
|> translate(
%,
x = leftSpacerPosition,
y = 0,
z = 0,
)
// Module for power switch including front plate and red rocker button
switchPosition = leftSpacerPosition + leftSpacerWidth / 2 + moduleWidth / 2
switchWidth = moduleWidth
// Switch Body
switchBody = boxModuleFn(width = moduleWidth)
// Switch Plate
switchPlateWidth = 20
switchPlateHeight = 30
switchPlateThickness = 3
switchPlateShape = startSketchOn(switchBody, face = END)
|> startProfile(
%,
at = [
-switchPlateWidth / 2,
-switchPlateHeight / 2
],
)
|> yLine(length = switchPlateHeight)
|> xLine(length = switchPlateWidth)
|> yLine(length = -switchPlateHeight)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
switchPlateBody = extrude(switchPlateShape, length = switchPlateThickness)
|> translate(
%,
x = switchPosition,
y = 0,
z = 0,
)
// Switch Button
switchButtonHeight = 26
switchButtonWidth = 15
switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -switchButtonWidth / 2))
|> startProfile(
%,
at = [
switchPlateThickness,
switchButtonHeight / 2
],
)
|> line(end = [3, -1])
|> arc(interiorAbsolute = [6, 0], endAbsolute = [12, -9])
|> line(endAbsolute = [
switchPlateThickness,
-switchButtonHeight / 2
])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
switchButtonBody = extrude(switchButtonShape, length = switchButtonWidth)
|> translate(
%,
x = switchPosition,
y = 0,
z = 0,
)
|> appearance(%, color = "#ff0000")
// Spacer between switch and plug modules for layout alignment
secondSpacerWidth = moduleWidth / 2
secondSpacerPosition = switchPosition + switchWidth / 2 + secondSpacerWidth / 2
secondSpacerBody = boxModuleFn(width = secondSpacerWidth)
|> translate(
%,
x = secondSpacerPosition,
y = 0,
z = 0,
)
// European power plug modules with circular sockets and two-pin holes
// 8 identical sockets, each with grounding notch and dual-pin recesses
powerPlugWidth = moduleWidth
powerPlugCount = 8
powerPlugOveralWidth = powerPlugWidth * powerPlugCount
firstPowerPlugPosition = secondSpacerPosition + secondSpacerWidth / 2 + powerPlugWidth / 2
lastPowerPlugPosition = firstPowerPlugPosition + powerPlugWidth * (powerPlugCount - 1)
powerPlugBody = boxModuleFn(width = powerPlugWidth)
|> translate(
%,
x = firstPowerPlugPosition,
y = 0,
z = 0,
)
plugShape = startSketchOn(powerPlugBody, face = END)
|> circle(%, center = [0, 0], radius = 17)
|> translate(
%,
x = firstPowerPlugPosition,
y = 0,
z = 0,
)
plugBody = extrude(plugShape, length = -20)
plugHoleDistance = 20
plugHoleShape = startSketchOn(plugBody, face = START)
|> circle(%, center = [-plugHoleDistance / 2, 0], radius = 3)
|> translate(
%,
x = firstPowerPlugPosition,
y = 0,
z = 0,
)
|> patternLinear2d(
%,
instances = 2,
distance = plugHoleDistance,
axis = [1, 0],
)
plugHoleBody = extrude(plugHoleShape, length = -5)
|> patternLinear3d(
%,
instances = powerPlugCount,
distance = powerPlugWidth,
axis = [1, 0, 0],
)
// Rightmost spacer to fill in remaining horizontal space
rightSpacerWidth = nestWidth / 2 - lastPowerPlugPosition - (powerPlugWidth / 2)
rightSpacerPosition = lastPowerPlugPosition + powerPlugWidth / 2 + rightSpacerWidth / 2
rightSpacerBody = boxModuleFn(width = rightSpacerWidth)
|> translate(
%,
x = rightSpacerPosition,
y = 0,
z = 0,
)
// Rack mounting holes on flanges, elongated for alignment flexibility
holeWidth = 25
holeDiameter = 5
holeStraightSegment = holeWidth - holeDiameter
holeVerticalDistance = faceplateHeight * 0.3
holeShapes = startProfile(
faceplateFrontFace,
at = [
-holeStraightSegment / 2,
holeDiameter / 2
],
)
|> xLine(length = holeStraightSegment)
|> tangentialArc(endAbsolute = [
holeStraightSegment / 2,
-holeDiameter / 2
])
|> xLine(length = -holeStraightSegment)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> translate(
%,
x = -faceplateWidth / 2 - (supportWidth / 2),
y = 0,
z = -holeVerticalDistance,
)
|> patternLinear2d(
%,
instances = 3,
distance = holeVerticalDistance,
axis = [0, 1],
)
|> patternLinear2d(
%,
instances = 2,
distance = faceplateWidth + supportWidth,
axis = [1, 0],
)
holeVoid = extrude(holeShapes, length = -supportThickness)

View File

@ -1,214 +0,0 @@
// Sash Window
// A traditional wooden sash window with two vertically sliding panels and a central locking mechanism
// Set units in millimeters (mm)
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
// Window state: 0 for closed, 1 for open
windowState = 0
// Basic window dimensions
windowWidth = 500
windowHeight = 1000
// Frame thickness and depth
frameWidth = 30
frameDepth = 50
// Number of divisions per sash (horizontal and vertical)
sashOpeningCountHorizontal = 2
sashOpeningCountVertical = 1
// Derived dimensions
sashWidth = windowWidth - (frameWidth * 2)
sashHeight = (windowHeight - (frameWidth * 2)) / 2 + frameWidth / 2
sashDepth = frameDepth / 2 - 2
sashTravelDistance = sashHeight * windowState * 0.8
// Function to create panel with frame and openings
fn panelFn(plane, offset, width, height, depth, perimeter, divisionThickness, openingCountHorizontal, openingCountVertical) {
// Create panel base shape
panelPlane = startSketchOn(offsetPlane(XZ, offset = offset))
panelShape = startProfile(panelPlane, at = [-width / 2, -height / 2])
|> yLine(length = height)
|> xLine(length = width)
|> yLine(length = -height)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
panelBody = extrude(panelShape, length = depth)
// Create opening grid within the panel
voidAreaWidth = width - (perimeter * 2)
voidAreaHeight = height - (perimeter * 2)
divisionTotalThicknessHorizontal = divisionThickness * openingCountHorizontal - divisionThickness
divisionTotalThicknessVertical = divisionThickness * openingCountVertical - divisionThickness
voidWidth = (voidAreaWidth - divisionTotalThicknessHorizontal) / openingCountHorizontal
voidHeight = (voidAreaHeight - divisionTotalThicknessVertical) / openingCountVertical
voidStepHorizontal = voidWidth + divisionThickness
voidStepVertical = voidHeight + divisionThickness
voidPlane = startSketchOn(panelBody, face = END)
voidShape = startProfile(
voidPlane,
at = [
-voidAreaWidth / 2,
-voidAreaHeight / 2
],
)
|> yLine(length = voidHeight)
|> xLine(length = voidWidth)
|> yLine(length = -voidHeight)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
%,
instances = openingCountHorizontal,
distance = voidStepHorizontal,
axis = [1, 0],
)
|> patternLinear2d(
%,
instances = openingCountVertical,
distance = voidStepVertical,
axis = [0, 1],
)
voidBody = extrude(voidShape, length = -depth)
|> appearance(color = "#a55e2c")
return panelBody
}
// Create main window frame
frame = panelFn(
plane = XZ,
offset = -frameDepth / 2,
width = windowWidth,
height = windowHeight,
depth = frameDepth,
perimeter = frameWidth,
divisionThickness = 10,
openingCountHorizontal = 1,
openingCountVertical = 1,
)
// Create bottom sliding sash
bottomSash = panelFn(
plane = XZ,
offset = (frameDepth / 2 - sashDepth) / 2,
width = sashWidth,
height = sashHeight,
depth = sashDepth,
perimeter = frameWidth,
divisionThickness = 10,
openingCountHorizontal = sashOpeningCountHorizontal,
openingCountVertical = sashOpeningCountVertical,
)
|> translate(
%,
x = 0,
y = 0,
z = frameWidth / 2 - (sashHeight / 2),
)
|> translate(
%,
x = 0,
y = 0,
z = sashTravelDistance,
) // open / close
// Latch mechanism on bottom sash
// Create latch plate
latchPlateWidth = 13
latchPlateLength = 30
latchPlateThickness = 1
latchPlatePlane = startSketchOn(offsetPlane(XY, offset = frameWidth / 2))
latchPlateShape = startProfile(
latchPlatePlane,
at = [
-latchPlateLength / 2,
-latchPlateWidth / 2
],
)
|> yLine(length = latchPlateWidth)
|> xLine(length = latchPlateLength)
|> yLine(length = -latchPlateWidth)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
latchPlateBody = extrude(latchPlateShape, length = latchPlateThickness)
|> translate(
%,
x = 0,
y = -frameDepth / 4,
z = 0,
)
|> translate(
%,
x = 0,
y = 0,
z = sashTravelDistance,
) // open / close
// Create latch cylinder
latchCylinderHeight = 5
latchCylinderPlane = startSketchOn(offsetPlane(latchPlatePlane, offset = latchPlateThickness))
latchCylinderShape = startProfile(latchCylinderPlane, at = [40, -1])
|> xLine(length = -35)
|> arc(interiorAbsolute = [-5, 0], endAbsolute = [5, 1])
|> xLine(length = 35)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
latchCylinderBody = extrude(latchCylinderShape, length = latchCylinderHeight)
|> translate(
%,
x = 0,
y = -frameDepth / 4,
z = 0,
)
|> translate(
%,
x = 0,
y = 0,
z = sashTravelDistance,
) // open / close
|> rotate(
%,
roll = 0,
pitch = 0,
yaw = -90 * windowState,
)
// Create top fixed sash
topSash = panelFn(
plane = XZ,
offset = -(frameDepth / 2 - sashDepth) / 2 - sashDepth,
width = sashWidth,
height = sashHeight,
depth = sashDepth,
perimeter = frameWidth,
divisionThickness = 10,
openingCountHorizontal = sashOpeningCountHorizontal,
openingCountVertical = sashOpeningCountVertical,
)
|> translate(
%,
x = 0,
y = 0,
z = sashHeight / 2 - (frameWidth / 2),
)
// Create latch nut on the top sash
latchNutPlane = startSketchOn(XZ)
latchNutShape = startProfile(
latchNutPlane,
at = [
-latchPlateLength / 2,
-latchPlateWidth / 2
],
)
|> yLine(length = latchPlateWidth)
|> xLine(length = latchPlateLength)
|> yLine(length = -latchPlateWidth)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
latchNutPlateBody = extrude(latchNutShape, length = latchPlateThickness)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

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